//! The module provides functions to generate gdk pixbuf c-source files.

use crate::stream_if::geometry;
use crate::stream_if::geometry::DrawDirective;
use crate::stream_if::geometry::Offset;
use crate::stream_if::geometry::Point;
use crate::stream_if::geometry::Rect;
use crate::stream_if::path_renderer::PathRenderer;
use raqote;
use std::fs::File;
use std::io::Write;

/// Defines a pixmap renderer that produces c code
pub struct CRenderer<'my_lifespan> {
    /// The file that is open for writing
    output_file: &'my_lifespan mut File,
    /// The icon name from which a c-struct name is derived
    icon_name: &'my_lifespan str,
    /// The target canvas of the 2d drawing library
    dt: raqote::DrawTarget,
}

/// The CRenderer struct provides some methods to write c header and footer
impl<'my_lifespan> CRenderer<'my_lifespan> {
    /// The function new initializes a CRenderer
    ///
    /// # Arguments
    ///
    /// * `output_file` - The file in which to write c code
    /// * `icon_name` - The c struct name
    /// * `viewport` - The drawing rectangle bounds
    ///
    /// # Panics
    ///
    /// Depends on library calls.
    ///
    pub(super) fn new(
        output_file: &'my_lifespan mut File,
        icon_name: &'my_lifespan str,
        viewport: &'my_lifespan Rect,
    ) -> CRenderer<'my_lifespan> {
        CRenderer {
            output_file: output_file,
            icon_name: icon_name,
            dt: raqote::DrawTarget::new(viewport.width as i32, viewport.height as i32),
        }
    }

    /// The function write_cimpl converts the graphics drawing to c format
    ///
    /// # Arguments
    ///
    /// * `view` - The bounding box of the visible area
    ///
    /// # Panics
    ///
    /// This function panics if the vector graphics cannot be written to a file.
    ///
    pub(super) fn write_cimpl(self: &mut Self) -> () {
        let buf = self.dt.get_data();

        write!(
            self.output_file,
            "\
/* generated by icon_artist */
static const struct {{
    guint  width;
    guint  height;
    guint  bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */
    guint8 pixel_data[{3}*4];
}} {0} = {{
    {1}, {2}, 4,
    {{
    \
            ",
            self.icon_name,
            self.dt.width(),
            self.dt.height(),
            buf.len()
        )
        .expect("Error at writing file");

        let mut pix_in_line: u32 = 0;
        for pixel in buf {
            let a = (pixel >> 24) & 0xffu32;
            let r = (pixel >> 16) & 0xffu32;
            let g = (pixel >> 8) & 0xffu32;
            let b = (pixel >> 0) & 0xffu32;
            write!(self.output_file, "{:>3},{:>3},{:>3},{:>3},", r, g, b, a)
                .expect("Error at writing file");
            pix_in_line += 1;
            if pix_in_line > 7 {
                /* lines are 140 characters wide */
                pix_in_line = 0;
                write!(self.output_file, "\n    ",).expect("Error at writing file");
            } else {
                /* only a sigle space*/
                write!(self.output_file, " ",).expect("Error at writing file");
            }
        }

        write!(self.output_file, "}}\n}};\n",).expect("Error at writing file");

        /* debug */
        /*
         * let filename = self.icon_name.to_owned() + ".png";
         * self.dt.write_png(filename).unwrap();
         */
    }
}

/// The CRenderer struct provides some methods that implement a PathRenderer
impl<'my_lifespan> PathRenderer for CRenderer<'my_lifespan> {
    /// The function render_path converts the vector graphics drawing directive path
    /// to a pixmap in c format
    /// # Arguments
    ///
    /// * `segs` - The segments of the path
    /// * `stroke` - The foreground color and width by which the path is stroked
    /// * `fill` - The background color by which the path is filled
    ///
    /// # Panics
    ///
    /// Depends on library calls.
    ///
    fn render_path(
        self: &mut Self,
        segs: &[DrawDirective],
        stroke: &Option<geometry::Pen>,
        fill: &Option<geometry::Color>,
    ) -> () {
        let mut section_start = Point { x: 0.0, y: 0.0 };
        let mut cursor = Point { x: 0.0, y: 0.0 };
        let mut direction = Offset { dx: 0.0, dy: 0.0 };
        let mut pb = raqote::PathBuilder::new();
        for seg in segs {
            match seg {
                DrawDirective::Move(target) => {
                    pb.move_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(cursor, *target);
                    cursor = *target;
                    section_start = *target;
                }
                DrawDirective::MoveRel(offset) => {
                    let target = Point::add(cursor, *offset);
                    pb.move_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = *offset;
                    cursor = target;
                    section_start = target;
                }
                DrawDirective::Line(target) => {
                    pb.line_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(cursor, *target);
                    cursor = *target;
                }
                DrawDirective::LineRel(offset) => {
                    let target = Point::add(cursor, *offset);
                    pb.line_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = *offset;
                    cursor = target;
                }
                DrawDirective::Continue(target) => {
                    /* assume last operation was a line */
                    pb.line_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(cursor, *target);
                    cursor = *target;
                }
                DrawDirective::ContinueRel(offset) => {
                    /* assume last operation was a line */
                    let target = Point::add(cursor, *offset);
                    pb.line_to(target.x, target.y);
                    /* update cursor and direction */
                    direction = *offset;
                    cursor = target;
                }
                DrawDirective::Curve(p1, p2, target) => {
                    pb.cubic_to(p1.x, p1.y, p2.x, p2.y, target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(*p2, *target);
                    cursor = *target;
                }
                DrawDirective::CurveRel(o_p1, o_p2, offset) => {
                    let p1 = Point::add(cursor, *o_p1);
                    let p2 = Point::add(cursor, *o_p2);
                    let target = Point::add(cursor, *offset);
                    pb.cubic_to(p1.x, p1.y, p2.x, p2.y, target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(p2, target);
                    cursor = target;
                }
                DrawDirective::Symmetric(p2, target) => {
                    let p1 = Point::add(cursor, direction);
                    pb.cubic_to(p1.x, p1.y, p2.x, p2.y, target.x, target.y);
                    /* update cursor and direction */
                    direction = Offset::delta(*p2, *target);
                    cursor = *target;
                }
                DrawDirective::SymmetricRel(o_p2, offset) => {
                    let p1 = Point::add(cursor, direction);
                    let p2 = Point::add(cursor, *o_p2);
                    let target = Point::add(cursor, *offset);
                    pb.cubic_to(p1.x, p1.y, p2.x, p2.y, target.x, target.y);
                    /* update cursor and direction */
                    cursor = target;
                    direction = Offset::delta(p2, target);
                }
                DrawDirective::Close => {
                    pb.close();
                    /* update cursor and direction */
                    direction = Offset::delta(cursor, section_start);
                    cursor = section_start;
                }
                DrawDirective::CloseRel => {
                    pb.close();
                    /* update cursor and direction */
                    direction = Offset::delta(cursor, section_start);
                    cursor = section_start;
                }
            }
        }
        let path = pb.finish();
        match fill {
            Some(ground_color) => {
                let options = raqote::DrawOptions {
                    blend_mode: raqote::BlendMode::SrcOver,
                    alpha: 1.0,
                    antialias: raqote::AntialiasMode::None,
                };
                self.dt.fill(
                    &path,
                    &raqote::Source::Solid(raqote::SolidSource {
                        r: ground_color.red,
                        g: ground_color.green,
                        b: ground_color.blue,
                        a: 0xff,
                    }),
                    &options,
                );
            }
            None => {}
        }
        match stroke {
            Some(pen) => {
                let options = raqote::DrawOptions {
                    blend_mode: raqote::BlendMode::SrcOver,
                    alpha: 1.0,
                    antialias: raqote::AntialiasMode::Gray,
                };
                self.dt.stroke(
                    &path,
                    &raqote::Source::Solid(raqote::SolidSource {
                        r: pen.color.red,
                        g: pen.color.green,
                        b: pen.color.blue,
                        a: 0xff,
                    }),
                    &raqote::StrokeStyle {
                        cap: raqote::LineCap::Round,
                        join: raqote::LineJoin::Round,
                        width: pen.width,
                        miter_limit: 0.0,
                        dash_array: vec![],
                        dash_offset: 0.0,
                    },
                    &options,
                );
            }
            None => {}
        }
    }
}

/*
Copyright 2024-2026 Andreas Warnke

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
