//!
//! Plot floats
//!

use super::*;

impl DiscNum for f64 {
    fn hole() -> Self {
        f64::NAN
    }
}

pub struct FloatTickFmt;

pub struct FloatFmt {
    offset: Option<f64>,
    axis: Axis,
    step: f64,
}
impl FloatFmt {
    pub fn step(&self) -> &f64 {
        &self.step
    }
    pub fn offset(&self) -> &Option<f64> {
        &self.offset
    }
}
impl crate::ticks::tick_fmt::TickFmt<f64> for FloatFmt {
    fn write_tick(&self, writer: &mut dyn std::fmt::Write, val: &f64) -> std::fmt::Result {
        let val = if let Some(offset) = self.offset {
            let val = val - offset;
            match self.axis {
                Axis::X => {
                    write!(writer, "j+")?;
                }
                Axis::Y => {
                    write!(writer, "k+")?;
                }
            }
            val
        } else {
            *val
        };

        util::write_interval_float(writer, val, Some(self.step))
    }
    fn write_where(&self, writer: &mut dyn std::fmt::Write) -> std::fmt::Result {
        if let Some(offset) = self.offset {
            match self.axis {
                Axis::X => {
                    write!(writer, "where j=")?;
                }
                Axis::Y => {
                    write!(writer, "where k=")?;
                }
            }
            util::write_interval_float(writer, offset, None)
        } else {
            Ok(())
        }
    }
}

impl TickDistGen<f64> for FloatTickFmt {
    type Res = TickDistribution<Vec<f64>, FloatFmt>;
    fn generate(
        self,
        data: &ticks::DataBound<f64>,
        canvas: &RenderFrameBound,
        _: IndexRequester,
    ) -> Self::Res {
        let range = &[data.min, data.max];
        let ideal_num_steps = canvas.ideal_num_steps;

        let tick_layout = TickLayout::new(&[1, 2, 5], ideal_num_steps, range);

        let (offset, ticks) = tick_layout.generate();

        let dash_size = compute_best_dash_1_2_5(
            tick_layout.step.scale(range, canvas.max),
            canvas.ideal_dash_size,
            tick_layout.normalized_step,
        );

        let axis = canvas.axis;

        TickDistribution {
            res: TickRes {
                dash_size: Some(dash_size),
            },
            iter: ticks,
            fmt: FloatFmt {
                offset,
                axis,
                step: tick_layout.step,
            },
        }
    }
}

impl plotnum::AsPlotnum for &f64 {
    type Target = f64;
    fn as_plotnum(&self) -> &Self::Target {
        self
    }
}

impl plotnum::AsPlotnum for &mut f64 {
    type Target = f64;
    fn as_plotnum(&self) -> &Self::Target {
        self
    }
}

impl HasDefaultTicks for f64 {
    type DefaultTicks = FloatTickFmt;
    fn default_ticks() -> FloatTickFmt {
        FloatTickFmt
    }
}

impl PlotNum for f64 {
    #[inline(always)]
    fn is_hole(&self) -> bool {
        self.is_nan()
    }
    #[inline(always)]
    fn scale(&self, range: &[f64; 2], max: f64) -> f64 {
        let val = *self;
        let diff = range[1] - range[0];
        let scale = max / diff;
        val * scale
    }
    #[inline(always)]
    fn unit_range(offset: Option<f64>) -> [f64; 2] {
        if let Some(o) = offset {
            [o - 1.0, o + 1.0]
        } else {
            [-1.0, 1.0]
        }
    }
}

fn round_up_to_nearest_multiple(val: f64, multiple: f64) -> f64 {
    ((val) / multiple).ceil() * multiple
}

struct TickLayout {
    step: f64,
    start_tick: f64,
    num_steps: u32,
    normalized_step: u32,
}
impl TickLayout {
    fn generate(&self) -> (Option<f64>, Vec<f64>) {
        let display_relative = {
            let tick_layout = self;
            let end =
                tick_layout.start_tick + ((tick_layout.num_steps - 1) as f64) * tick_layout.step;

            let mut start_s = String::new();
            let mut end_s = String::new();

            util::write_interval_float(
                &mut start_s,
                tick_layout.start_tick,
                Some(tick_layout.step),
            )
            .unwrap();
            util::write_interval_float(&mut end_s, end, Some(tick_layout.step)).unwrap();

            if start_s.len() > 7 || end_s.len() > 7 {
                Some(tick_layout.start_tick)
            } else {
                None
            }
        };

        let mut ticks = Vec::with_capacity(usize::try_from(self.num_steps).unwrap());
        for a in 0..self.num_steps {
            let position = self.start_tick + self.step * (a as f64);

            ticks.push(position);
        }
        (display_relative, ticks)
    }

    fn new(good_steps: &[u32], ideal_num_steps: u32, range_all: &[f64; 2]) -> TickLayout {
        let ideal_num_steps = ideal_num_steps.max(2);

        let range = range_all[1] - range_all[0];

        let rough_step = range / (ideal_num_steps - 1) as f64;

        let step_power = 10.0f64.powf(rough_step.log10().floor());

        let cc = good_steps.iter().map(|&normalized_step| {
            assert!(normalized_step > 0);
            let step = normalized_step as f64 * step_power;

            let start_tick = round_up_to_nearest_multiple(range_all[0], step);

            let num_steps = {
                let mut counter = start_tick;
                let mut res = 0;
                for a in 0.. {
                    if counter > range_all[1] {
                        res = a;
                        break;
                    }

                    assert!(step + counter > counter, "{:?}", (step, range_all));
                    counter += step;
                }
                res
            };

            let res = TickLayout {
                step,
                normalized_step,
                num_steps,
                start_tick,
            };

            (res, (num_steps as i32 - ideal_num_steps as i32).abs())
        });

        let best = cc.min_by(|a, b| a.1.cmp(&b.1)).unwrap();
        let best = best.0;
        assert!(best.num_steps >= 2);
        best
    }
}

impl HasZero for f64 {
    #[inline(always)]
    fn zero() -> f64 {
        0.0
    }
}
