use crate::context::palette::Palette;
use cairo::Context;
use cairo::{ImageSurface, Surface, SvgSurface};
use std::fs::File;

/// container that holds everything to render surface
pub(crate) trait RenderContainer {
    /// render command
    fn render(&self);
    /// get render context just in case
    fn context(&self) -> &Context;
    /// surface to create cairo context
    fn surface(&self) -> &Surface;

    /// initialize RenderContainer
    fn init(&self, width: f64, height: f64, line_size: f64);

    /// plot a path
    fn plot_path(&self, path: &Vec<[f64; 2]>, fill: bool);

    fn put_path_on_stack(&self, path: &Vec<[f64; 2]>) {
        let context = self.context();
        let mut init = true;
        for &[x, y] in path.iter() {
            if init {
                context.move_to(x, y);
                init = false;
            } else {
                context.line_to(x, y);
            }
        }
        context.close_path();
    }
}

/// container to render svg images
pub struct SvgRenderContainer {
    surface: SvgSurface,
    context: Context,
}

impl SvgRenderContainer {
    pub fn new(surface: SvgSurface, _: Palette) -> Self {
        let context = Context::new(&surface);
        SvgRenderContainer { surface, context }
    }
}

impl RenderContainer for SvgRenderContainer {
    fn render(&self) {
        // nothing to do
    }

    fn context(&self) -> &Context {
        &self.context
    }

    fn surface(&self) -> &Surface {
        &self.surface as &Surface
    }

    fn init(&self, _width: f64, _height: f64, line_size: f64) {
        self.context.set_line_width(line_size);
    }

    /// plot a path
    fn plot_path(&self, path: &Vec<[f64; 2]>, fill: bool) {
        self.put_path_on_stack(path);
        let context = self.context();
        if fill {
            context.fill()
        } else {
            context.stroke();
        }
    }
}

/// container to render png images
pub struct PngRenderContainer {
    /// output path of the png
    path: String,
    surface: ImageSurface,
    context: Context,
    palette: Palette,
}

impl PngRenderContainer {
    pub fn new(path: String, surface: ImageSurface, palette: Palette) -> Self {
        let context = Context::new(&surface);
        PngRenderContainer {
            path,
            surface,
            context,
            palette,
        }
    }
}

impl RenderContainer for PngRenderContainer {
    fn render(&self) {
        let mut file = File::create(&self.path).expect("Couldn't create 'file.png'");
        match self.surface.write_to_png(&mut file) {
            Ok(_) => println!("{}, created", self.path),
            Err(_) => println!("Error create file.png"),
        }
    }

    fn context(&self) -> &Context {
        &self.context
    }

    fn surface(&self) -> &Surface {
        &self.surface as &Surface
    }

    fn init(&self, width: f64, height: f64, line_size: f64) {
        // set background color
        self.context.set_source_rgb(
            self.palette.background_color.red as f64,
            self.palette.background_color.green as f64,
            self.palette.background_color.blue as f64,
        );
        self.context.rectangle(0., 0., width, height);
        self.context.fill();

        // configure line
        self.context.set_source_rgb(
            self.palette.fill_color.red as f64,
            self.palette.fill_color.green as f64,
            self.palette.fill_color.blue as f64,
        );
        self.context.set_line_width(line_size);
    }

    /// plot a path
    fn plot_path(&self, path: &Vec<[f64; 2]>, fill: bool) {
        if fill {
            self.put_path_on_stack(path);
            self.context.fill()
        } else {
            // fill with background
            self.context.set_source_rgb(
                self.palette.background_color.red as f64,
                self.palette.background_color.green as f64,
                self.palette.background_color.blue as f64,
            );
            self.put_path_on_stack(path);
            self.context.fill();
            // set drawing color back to normal
            self.context.set_source_rgb(
                self.palette.fill_color.red as f64,
                self.palette.fill_color.green as f64,
                self.palette.fill_color.blue as f64,
            );
            self.put_path_on_stack(path);
            self.context.stroke();
        }
    }
}