use super::*;

#[derive(Clone, Copy, Debug)]
pub(crate) struct CatPalette {
    pub(crate) background: Color,
    pub(crate) accent: Color,
    pub(crate) head: Color,
    pub(crate) ear_inner: Color,
    pub(crate) muzzle: Color,
    pub(crate) eye: Color,
    pub(crate) pupil: Color,
    pub(crate) nose: Color,
    pub(crate) outline: Color,
    pub(crate) marking: Color,
}

impl CatPalette {
    pub(crate) fn from_genome(genome: &CatGenome) -> Self {
        let hue = genome.base_hue;
        let head = hsl_to_color(
            hue,
            0.42 + genome.head_saturation * 0.25,
            0.55 + genome.head_lightness * 0.16,
        );
        let background = hsl_to_color(
            (hue + 180.0 + genome.background_shift * 40.0) % 360.0,
            0.25 + genome.background_sat * 0.20,
            0.90,
        );
        let accent = hsl_to_color(
            (hue + 18.0 + genome.accent_shift * 60.0) % 360.0,
            0.34 + genome.accent_sat * 0.20,
            0.80,
        );

        Self {
            background,
            accent,
            head,
            ear_inner: hsl_to_color(
                hue - 6.0,
                0.50 + genome.ear_inner_sat * 0.20,
                0.72 + genome.ear_inner_light * 0.12,
            ),
            muzzle: hsl_to_color(
                hue + 8.0,
                0.18 + genome.muzzle_sat * 0.16,
                0.84 + genome.muzzle_light * 0.10,
            ),
            eye: hsl_to_color(
                genome.eye_hue,
                0.65 + genome.eye_sat * 0.20,
                0.50 + genome.eye_light * 0.12,
            ),
            pupil: Color::rgb(28, 24, 18),
            nose: hsl_to_color(
                344.0 + genome.nose_hue * 18.0,
                0.58 + genome.nose_sat * 0.18,
                0.66 + genome.nose_light * 0.10,
            ),
            outline: Color::rgb(64, 45, 32),
            marking: hsl_to_color(
                hue + genome.marking_hue_shift * 24.0,
                0.25 + genome.marking_sat * 0.20,
                0.42 + genome.marking_light * 0.16,
            ),
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct CatGenome {
    pub(crate) base_hue: f32,
    pub(crate) eye_hue: f32,
    pub(crate) head_saturation: f32,
    pub(crate) head_lightness: f32,
    pub(crate) background_shift: f32,
    pub(crate) background_sat: f32,
    pub(crate) accent_shift: f32,
    pub(crate) accent_sat: f32,
    pub(crate) ear_inner_sat: f32,
    pub(crate) ear_inner_light: f32,
    pub(crate) muzzle_sat: f32,
    pub(crate) muzzle_light: f32,
    pub(crate) eye_sat: f32,
    pub(crate) eye_light: f32,
    pub(crate) nose_hue: f32,
    pub(crate) nose_sat: f32,
    pub(crate) nose_light: f32,
    pub(crate) marking_hue_shift: f32,
    pub(crate) marking_sat: f32,
    pub(crate) marking_light: f32,
    pub(crate) head_width: f32,
    pub(crate) head_height: f32,
    pub(crate) head_drop: f32,
    pub(crate) ear_width: f32,
    pub(crate) ear_height: f32,
    pub(crate) ear_tilt: f32,
    pub(crate) muzzle_width: f32,
    pub(crate) muzzle_height: f32,
    pub(crate) eye_spacing: f32,
    pub(crate) eye_width: f32,
    pub(crate) eye_height: f32,
    pub(crate) pupil_width: f32,
    pub(crate) whisker_len: f32,
    pub(crate) whisker_tilt: f32,
    pub(crate) smile_width: f32,
    pub(crate) smile_depth: f32,
    pub(crate) accent_band_height: f32,
    pub(crate) forehead_mark: f32,
    pub(crate) cheek_spots: f32,
    pub(crate) stripe_count: u8,
}

impl CatGenome {
    pub(crate) fn from_identity(identity: &AvatarIdentity, rng: &mut StdRng) -> Self {
        let mut noise =
            |idx: usize| (identity.unit_f32(idx) + rng.random_range(0.0..0.03)).min(1.0);
        Self {
            base_hue: 12.0 + identity.unit_f32(0) * 300.0,
            eye_hue: 45.0 + identity.unit_f32(1) * 120.0,
            head_saturation: noise(2),
            head_lightness: noise(3),
            background_shift: noise(4),
            background_sat: noise(5),
            accent_shift: noise(6),
            accent_sat: noise(7),
            ear_inner_sat: noise(8),
            ear_inner_light: noise(9),
            muzzle_sat: noise(10),
            muzzle_light: noise(11),
            eye_sat: noise(12),
            eye_light: noise(13),
            nose_hue: noise(14),
            nose_sat: noise(15),
            nose_light: noise(16),
            marking_hue_shift: identity.unit_f32(17) * 2.0 - 1.0,
            marking_sat: noise(18),
            marking_light: noise(19),
            head_width: noise(20),
            head_height: noise(21),
            head_drop: noise(22),
            ear_width: noise(23),
            ear_height: noise(24),
            ear_tilt: identity.unit_f32(25) * 2.0 - 1.0,
            muzzle_width: noise(26),
            muzzle_height: noise(27),
            eye_spacing: noise(28),
            eye_width: noise(29),
            eye_height: noise(30),
            pupil_width: noise(31),
            whisker_len: noise(32),
            whisker_tilt: identity.unit_f32(33) * 2.0 - 1.0,
            smile_width: noise(34),
            smile_depth: noise(35),
            accent_band_height: noise(36),
            forehead_mark: noise(37),
            cheek_spots: noise(38),
            stripe_count: 2 + (identity.byte(39) % 4),
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct EarSpec {
    pub(crate) outer: [Point<i32>; 3],
    pub(crate) inner: [Point<i32>; 3],
}

impl EarSpec {
    pub(crate) fn left(
        center_x: i32,
        center_y: i32,
        head_rx: i32,
        head_ry: i32,
        ear_width: i32,
        ear_height: i32,
        ear_tilt: f32,
    ) -> Self {
        let base_x = center_x - head_rx / 2;
        let base_y = center_y - head_ry + 12;
        let tip_shift = (ear_width as f32 * 0.35 * ear_tilt) as i32;
        Self {
            outer: [
                Point::new(base_x - ear_width / 2, base_y + ear_height / 2),
                Point::new(base_x + ear_width / 3 + tip_shift, base_y - ear_height),
                Point::new(base_x + ear_width, base_y + ear_height / 3),
            ],
            inner: [
                Point::new(base_x - ear_width / 6, base_y + ear_height / 4),
                Point::new(
                    base_x + ear_width / 4 + tip_shift / 2,
                    base_y - (ear_height as f32 * 0.55) as i32,
                ),
                Point::new(
                    base_x + (ear_width as f32 * 0.6) as i32,
                    base_y + ear_height / 8,
                ),
            ],
        }
    }

    pub(crate) fn right(
        center_x: i32,
        center_y: i32,
        head_rx: i32,
        head_ry: i32,
        ear_width: i32,
        ear_height: i32,
        ear_tilt: f32,
    ) -> Self {
        let base_x = center_x + head_rx / 2;
        let base_y = center_y - head_ry + 12;
        let tip_shift = (ear_width as f32 * 0.35 * ear_tilt) as i32;
        Self {
            outer: [
                Point::new(base_x - ear_width, base_y + ear_height / 3),
                Point::new(base_x - ear_width / 3 - tip_shift, base_y - ear_height),
                Point::new(base_x + ear_width / 2, base_y + ear_height / 2),
            ],
            inner: [
                Point::new(
                    base_x - (ear_width as f32 * 0.6) as i32,
                    base_y + ear_height / 8,
                ),
                Point::new(
                    base_x - ear_width / 4 - tip_shift / 2,
                    base_y - (ear_height as f32 * 0.55) as i32,
                ),
                Point::new(base_x + ear_width / 6, base_y + ear_height / 4),
            ],
        }
    }
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn draw_background_accent(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    head_rx: i32,
    head_ry: i32,
    accent: Color,
    accent_band_height: f32,
    background: AvatarBackground,
    identity: &AvatarIdentity,
) {
    if background != AvatarBackground::Themed {
        draw_decorative_background(image, background, accent, identity);
        return;
    }
    let width = image.width() as i32;
    let stripe_top = center_y - head_ry - 18;
    let stripe_height = ((head_ry as f32) * (0.25 + accent_band_height * 0.45)) as i32;

    draw_filled_rect_mut(
        image,
        Rect::at(0, stripe_top.max(0)).of_size(width as u32, stripe_height.max(1) as u32),
        accent.into(),
    );
    draw_filled_circle_mut(
        image,
        (center_x + head_rx / 2, center_y - head_ry / 2),
        head_ry / 3,
        Color::rgba(accent.0[0], accent.0[1], accent.0[2], 180).into(),
    );
}

pub(crate) fn draw_ear(
    image: &mut RgbaImage,
    spec: EarSpec,
    outer_color: Color,
    inner_color: Color,
    outline: Color,
) {
    draw_polygon_mut(image, &spec.outer, outer_color.into());
    draw_polygon_mut(image, &spec.inner, inner_color.into());

    for edge in spec.outer.windows(2) {
        draw_antialiased_line_segment_mut(
            image,
            (edge[0].x, edge[0].y),
            (edge[1].x, edge[1].y),
            outline.into(),
            interpolate,
        );
    }
    draw_antialiased_line_segment_mut(
        image,
        (spec.outer[2].x, spec.outer[2].y),
        (spec.outer[0].x, spec.outer[0].y),
        outline.into(),
        interpolate,
    );
}

pub(crate) fn draw_eyes(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    head_rx: i32,
    head_ry: i32,
    palette: CatPalette,
    genome: CatGenome,
) {
    let eye_offset_x = (head_rx as f32 * (0.31 + genome.eye_spacing * 0.18)) as i32;
    let eye_y = center_y - head_ry / 6;
    let eye_rx = (head_rx as f32 * (0.12 + genome.eye_width * 0.10)) as i32;
    let eye_ry = (head_ry as f32 * (0.11 + genome.eye_height * 0.10)) as i32;
    let pupil_ry = (eye_ry as f32 * 0.90) as i32;
    let pupil_rx = ((eye_rx as f32) * (0.12 + genome.pupil_width * 0.18)) as i32;

    for eye_x in [center_x - eye_offset_x, center_x + eye_offset_x] {
        draw_filled_ellipse_mut(image, (eye_x, eye_y), eye_rx, eye_ry, palette.eye.into());
        draw_filled_ellipse_mut(
            image,
            (eye_x, eye_y),
            pupil_rx,
            pupil_ry,
            palette.pupil.into(),
        );
        draw_filled_circle_mut(
            image,
            (eye_x - eye_rx / 3, eye_y - eye_ry / 3),
            (eye_rx as f32 * 0.15) as i32,
            Color::rgba(255, 255, 255, 220).into(),
        );
    }
}

pub(crate) fn draw_nose_and_mouth(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    head_rx: i32,
    head_ry: i32,
    palette: CatPalette,
    genome: CatGenome,
) {
    let nose_y = center_y + head_ry / 7;
    let nose_half_width = (head_rx as f32 * (0.08 + genome.muzzle_width * 0.05)) as i32;
    let nose_height = (head_ry as f32 * (0.08 + genome.muzzle_height * 0.05)) as i32;
    let nose = [
        Point::new(center_x - nose_half_width, nose_y),
        Point::new(center_x + nose_half_width, nose_y),
        Point::new(center_x, nose_y + nose_height),
    ];
    draw_polygon_mut(image, &nose, palette.nose.into());

    let mouth_top = nose_y + nose_height;
    draw_line_segment_mut(
        image,
        (center_x as f32, mouth_top as f32),
        (center_x as f32, (mouth_top + head_ry / 8) as f32),
        palette.outline.into(),
    );

    let smile_radius = (head_rx as f32 * (0.08 + genome.smile_width * 0.10)) as i32;
    draw_smile_arc(
        image,
        center_x - smile_radius,
        mouth_top + smile_radius / 2,
        smile_radius,
        palette.outline,
        genome.smile_depth,
    );
    draw_smile_arc(
        image,
        center_x + smile_radius,
        mouth_top + smile_radius / 2,
        smile_radius,
        palette.outline,
        genome.smile_depth,
    );
}

pub(crate) fn draw_smile_arc(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    radius: i32,
    color: Color,
    smile_depth: f32,
) {
    for step in 20..=160 {
        let theta = (step as f32).to_radians();
        let x = center_x as f32 + theta.cos() * radius as f32 * 0.55;
        let y = center_y as f32 + theta.sin() * radius as f32 * (0.24 + smile_depth * 0.28);
        draw_filled_circle_mut(image, (x.round() as i32, y.round() as i32), 1, color.into());
    }
}

pub(crate) fn draw_whiskers(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    head_rx: i32,
    head_ry: i32,
    color: Color,
    genome: CatGenome,
) {
    let muzzle_y = center_y + head_ry / 5;
    let left_start = center_x - head_rx / 6;
    let right_start = center_x + head_rx / 6;
    let whisker_len = (head_rx as f32 * (0.58 + genome.whisker_len * 0.42)) as i32;
    let whisker_slope = (genome.whisker_tilt * 12.0) as i32;

    for offset in [-12, 0, 12] {
        draw_antialiased_line_segment_mut(
            image,
            (left_start, muzzle_y + offset),
            (
                left_start - whisker_len,
                muzzle_y + offset - 8 + whisker_slope,
            ),
            color.into(),
            interpolate,
        );
        draw_antialiased_line_segment_mut(
            image,
            (right_start, muzzle_y + offset),
            (
                right_start + whisker_len,
                muzzle_y + offset - 8 - whisker_slope,
            ),
            color.into(),
            interpolate,
        );
    }
}

pub(crate) fn draw_cat_markings(
    image: &mut RgbaImage,
    center_x: i32,
    center_y: i32,
    head_rx: i32,
    head_ry: i32,
    color: Color,
    genome: CatGenome,
) {
    let stripe_count = genome.stripe_count as i32;
    let forehead_y = center_y - head_ry / 2;
    let stripe_spacing = (head_rx / 5).max(6);
    let stripe_length = ((head_ry as f32) * (0.14 + genome.forehead_mark * 0.12)) as i32;

    for stripe in 0..stripe_count {
        let offset = stripe - stripe_count / 2;
        let x = center_x + offset * stripe_spacing / 2;
        draw_line_segment_mut(
            image,
            (x as f32, forehead_y as f32),
            ((x + offset * 2) as f32, (forehead_y + stripe_length) as f32),
            color.into(),
        );
    }

    if genome.cheek_spots > 0.35 {
        let cheek_y = center_y + head_ry / 5;
        let cheek_x = (head_rx as f32 * 0.55) as i32;
        let cheek_radius = ((head_rx as f32) * (0.05 + genome.cheek_spots * 0.04)) as i32;
        draw_filled_circle_mut(
            image,
            (center_x - cheek_x, cheek_y),
            cheek_radius,
            Color::rgba(color.0[0], color.0[1], color.0[2], 120).into(),
        );
        draw_filled_circle_mut(
            image,
            (center_x + cheek_x, cheek_y),
            cheek_radius,
            Color::rgba(color.0[0], color.0[1], color.0[2], 120).into(),
        );
    }
}

pub(crate) fn hsl_to_color(hue: f32, saturation: f32, lightness: f32) -> Color {
    let rgb_u8: Srgb<u8> = Srgb::from_color(Hsl::new(hue, saturation, lightness)).into_format();
    Color::rgb(rgb_u8.red, rgb_u8.green, rgb_u8.blue)
}

pub(crate) fn background_fill(background: AvatarBackground, themed: Color) -> Color {
    match background {
        AvatarBackground::Themed => themed,
        AvatarBackground::White => Color::rgb(255, 255, 255),
        AvatarBackground::Black => Color::rgb(0, 0, 0),
        AvatarBackground::Dark => Color::rgb(17, 24, 39),
        AvatarBackground::Light => Color::rgb(248, 250, 247),
        AvatarBackground::Transparent => Color::rgba(255, 255, 255, 0),
        AvatarBackground::PolkaDot
        | AvatarBackground::Striped
        | AvatarBackground::Checkerboard
        | AvatarBackground::Grid => Color::rgb(248, 250, 247),
        AvatarBackground::Sunrise => Color::rgb(255, 244, 214),
        AvatarBackground::Ocean => Color::rgb(221, 246, 252),
        AvatarBackground::Starry => Color::rgb(17, 24, 39),
    }
}
