const DEFAULT_MAP_SIZE: usize = 256;

pub struct Map {
    duration: f32,
    map: Vec<f32>,
}

impl Map {
    pub fn from_function(duration: f32, generator: impl Fn(f32) -> f32) -> Self {
        Self::from_function_sized(duration, generator, DEFAULT_MAP_SIZE)
    }

    pub fn from_function_sized(duration: f32, generator: impl Fn(f32) -> f32, size: usize) -> Self {
        Self {
            duration: duration.max(0.0),
            map: (0..size)
                .map(|i| {
                    generator(duration * i as f32 / size as f32)
                        .max(0.0)
                        .min(1.0)
                })
                .collect(),
        }
    }

    pub fn from_data(duration: f32, map: &[f32]) -> Self {
        Self {
            duration: duration.max(0.0),
            map: map.iter().map(|v| v.max(0.0).min(1.0)).collect(),
        }
    }

    pub fn value(&self, x: f32) -> f32 {
        let y_last = *self.map.last().unwrap_or(&0.0);
        if self.duration == 0.0 {
            y_last
        } else {
            let i = (x / self.duration) * self.map.len() as f32;
            let i1 = i.floor() as usize;
            let i2 = i.ceil() as usize;
            let y1 = self.map.get(i1).unwrap_or(&y_last);
            let y2 = self.map.get(i2).unwrap_or(&y_last);
            y1 * (1.0 - i.fract()) + y2 * i.fract()
        }
    }
}

// ============================================================================

pub fn default_hold() -> Map {
    fade_out(3.0)
}

pub fn default_release() -> Map {
    none()
}

pub fn fade_in(duration: f32) -> Map {
    Map::from_function(duration, |t| {
        // FIXME: Temporary hack to avoid automatic note disactivation on low amplitude
        let threshold = 0.01;
        t / duration + threshold
    })
}

pub fn fade_out(duration: f32) -> Map {
    Map::from_function(duration, |t| 1.0 / (7.0 * t / duration).exp())
}

pub fn fade(in_duration: f32, out_duration: f32) -> Map {
    Map::from_function(in_duration + out_duration, |t| {
        if t < in_duration {
            // FIXME: Temporary hack to avoid automatic note disactivation on low amplitude
            let threshold = 0.01;
            t / in_duration + threshold
        } else {
            1.0 / (7.0 * (t - in_duration) / out_duration).exp()
        }
    })
}

pub fn none() -> Map {
    Map::from_data(0.0, &[1.0])
}

pub fn _mute() -> Map {
    Map::from_data(0.0, &[0.0])
}
