use cord_trig::ir::{NodeId, TrigOp};
use crate::parser::ExprParser;

impl<'a> ExprParser<'a> {
    pub(crate) fn parse_ngon(&mut self, n: u32, args: &[NodeId]) -> Result<NodeId, String> {
        if n < 3 {
            return Err(format!("{n}-gon: need at least 3 sides"));
        }

        let (is_reg, rest) = if !args.is_empty() {
            if let TrigOp::Const(v) = self.graph.nodes[args[0] as usize] {
                if v.is_nan() { (true, &args[1..]) } else { (false, args) }
            } else {
                (false, args)
            }
        } else {
            (false, args)
        };

        if is_reg {
            let side = if rest.is_empty() {
                self.graph.push(TrigOp::Const(1.0))
            } else if rest.len() == 1 {
                rest[0]
            } else {
                return Err(format!("{n}-gon(reg[, side])"));
            };
            return self.build_regular_ngon(n, side);
        }

        if args.len() == 1 {
            return self.build_regular_ngon(n, args[0]);
        }

        let expected = 2 * n as usize - 3;
        if args.len() != expected {
            return Err(format!(
                "{n}-gon: expected 1 (side), reg, or {expected} (s,a,s,...) params, got {}",
                args.len()
            ));
        }

        let mut params = Vec::with_capacity(expected);
        for (i, &arg) in args.iter().enumerate() {
            match self.graph.nodes.get(arg as usize) {
                Some(TrigOp::Const(v)) => params.push(*v),
                _ => return Err(format!("{n}-gon: param {} must be a constant", i + 1)),
            }
        }

        let vertices = construct_polygon_sas(n, &params)?;
        self.build_polygon_sdf(&vertices)
    }

    fn build_regular_ngon(&mut self, n: u32, side: NodeId) -> Result<NodeId, String> {
        let ix = self.get_x();
        let iy = self.get_y();

        let inv_2tan = 1.0 / (2.0 * (std::f64::consts::PI / n as f64).tan());
        let scale = self.graph.push(TrigOp::Const(inv_2tan));
        let inradius = self.graph.push(TrigOp::Mul(side, scale));

        let mut result: Option<NodeId> = None;
        for i in 0..n {
            let angle = 2.0 * std::f64::consts::PI * i as f64 / n as f64;
            let cx = self.graph.push(TrigOp::Const(angle.cos()));
            let cy = self.graph.push(TrigOp::Const(angle.sin()));
            let dx = self.graph.push(TrigOp::Mul(ix, cx));
            let dy = self.graph.push(TrigOp::Mul(iy, cy));
            let dot = self.graph.push(TrigOp::Add(dx, dy));
            let edge = self.graph.push(TrigOp::Sub(dot, inradius));
            result = Some(match result {
                None => edge,
                Some(prev) => self.graph.push(TrigOp::Max(prev, edge)),
            });
        }

        Ok(result.unwrap())
    }

    fn build_polygon_sdf(&mut self, vertices: &[(f64, f64)]) -> Result<NodeId, String> {
        let n = vertices.len();
        let ix = self.get_x();
        let iy = self.get_y();

        let mut result: Option<NodeId> = None;
        for i in 0..n {
            let j = (i + 1) % n;
            let (x0, y0) = vertices[i];
            let (x1, y1) = vertices[j];

            let dx = x1 - x0;
            let dy = y1 - y0;
            let len = (dx * dx + dy * dy).sqrt();
            if len < 1e-15 { continue; }

            let nx = dy / len;
            let ny = -dx / len;
            let offset = nx * x0 + ny * y0;

            let cnx = self.graph.push(TrigOp::Const(nx));
            let cny = self.graph.push(TrigOp::Const(ny));
            let cd  = self.graph.push(TrigOp::Const(offset));

            let dot_x = self.graph.push(TrigOp::Mul(ix, cnx));
            let dot_y = self.graph.push(TrigOp::Mul(iy, cny));
            let dot   = self.graph.push(TrigOp::Add(dot_x, dot_y));
            let dist   = self.graph.push(TrigOp::Sub(dot, cd));

            result = Some(match result {
                None => dist,
                Some(prev) => self.graph.push(TrigOp::Max(prev, dist)),
            });
        }

        result.ok_or_else(|| "degenerate polygon".into())
    }
}

fn construct_polygon_sas(n: u32, params: &[f64]) -> Result<Vec<(f64, f64)>, String> {
    use std::f64::consts::PI;

    let mut vertices = Vec::with_capacity(n as usize);
    let mut x = 0.0_f64;
    let mut y = 0.0_f64;
    let mut heading = 0.0_f64;

    vertices.push((x, y));

    for i in 0..(n as usize - 1) {
        let side = params[i * 2];
        if side <= 0.0 {
            return Err(format!("side {} must be positive", i + 1));
        }
        x += side * heading.cos();
        y += side * heading.sin();
        vertices.push((x, y));

        if i * 2 + 1 < params.len() {
            let interior = params[i * 2 + 1];
            heading += PI - interior;
        }
    }

    let cx: f64 = vertices.iter().map(|v| v.0).sum::<f64>() / n as f64;
    let cy: f64 = vertices.iter().map(|v| v.1).sum::<f64>() / n as f64;
    for v in &mut vertices {
        v.0 -= cx;
        v.1 -= cy;
    }

    let mut area2 = 0.0;
    for i in 0..vertices.len() {
        let j = (i + 1) % vertices.len();
        area2 += vertices[i].0 * vertices[j].1 - vertices[j].0 * vertices[i].1;
    }
    if area2 < 0.0 {
        vertices.reverse();
    }

    Ok(vertices)
}

#[cfg(test)]
mod tests {
    use crate::parse_expr;
    use cord_trig::eval::evaluate;

    #[test]
    fn ngon_square() {
        let g = parse_expr("4-gon(2)").unwrap();
        assert!((evaluate(&g, 1.0, 0.0, 0.0) - 0.0).abs() < 1e-6);
        assert!((evaluate(&g, 0.0, 0.0, 0.0) - -1.0).abs() < 1e-6);
    }

    #[test]
    fn ngon_function_syntax() {
        let g = parse_expr("ngon(4, 2)").unwrap();
        assert!((evaluate(&g, 1.0, 0.0, 0.0) - 0.0).abs() < 1e-6);
    }

    #[test]
    fn ngon_reg_keyword() {
        let g = parse_expr("4-gon(reg, 2)").unwrap();
        assert!((evaluate(&g, 1.0, 0.0, 0.0) - 0.0).abs() < 1e-6);
    }

    #[test]
    fn ngon_reg_default_side() {
        let g = parse_expr("4-gon(reg)").unwrap();
        assert!((evaluate(&g, 0.5, 0.0, 0.0) - 0.0).abs() < 1e-6);
        assert!((evaluate(&g, 0.0, 0.0, 0.0) - -0.5).abs() < 1e-6);
    }

    #[test]
    fn ngon_reg_default_triangle() {
        let g = parse_expr("3-gon(reg)").unwrap();
        assert!(evaluate(&g, 0.0, 0.0, 0.0) < 0.0);
    }

    #[test]
    fn ngon_sas_equilateral() {
        use std::f64::consts::PI;
        let src = format!("3-gon(2, {}, 2)", PI / 3.0);
        let g = parse_expr(&src).unwrap();
        assert!(evaluate(&g, 0.0, 0.0, 0.0) < 0.0);
    }

    #[test]
    fn ngon_sas_right_triangle() {
        use std::f64::consts::FRAC_PI_2;
        let src = format!("3-gon(3, {}, 4)", FRAC_PI_2);
        let g = parse_expr(&src).unwrap();
        assert!(evaluate(&g, 0.0, 0.0, 0.0) < 0.0);
    }

    #[test]
    fn ngon_sas_square() {
        use std::f64::consts::FRAC_PI_2;
        let src = format!("4-gon(2, {0}, 2, {0}, 2)", FRAC_PI_2);
        let g = parse_expr(&src).unwrap();
        assert!(evaluate(&g, 0.0, 0.0, 0.0) < 0.0);
    }
}
