// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Scalable Vector Graphics (SVG) file writer

use microcad_core::*;

use crate::svg::{SvgTagAttributes, canvas::Canvas};

/// SVG writer.
pub struct SvgWriter {
    /// The writer (e.g. a file).
    writer: Box<dyn std::io::Write>,
    /// Indentation level.
    level: usize,
    /// The canvas.
    canvas: Canvas,
}

impl SvgWriter {
    /// Create new SvgWriter
    /// # Arguments
    /// - `w`: Output writer
    /// - `size`: Size of the canvas.
    /// - `scale`: Scale of the output
    pub fn new_canvas(
        mut writer: Box<dyn std::io::Write>,
        size: Option<Size2>,
        content_rect: Rect,
        scale: Option<Scalar>,
    ) -> std::io::Result<Self> {
        let size = match size {
            Some(size) => size,
            None => Size2 {
                width: content_rect.width(),
                height: content_rect.height(),
            },
        };
        let x = 0;
        let y = 0;
        let w = size.width;
        let h = size.height;
        let canvas = Canvas::new_centered_content(size, content_rect, scale);

        writeln!(&mut writer, "<?xml version='1.0' encoding='UTF-8'?>")?;
        writeln!(
            &mut writer,
            "<svg version='1.1' xmlns='http://www.w3.org/2000/svg' viewBox='{x} {y} {w} {h}' width='{w}mm' height='{h}mm'>",
        )?;
        writeln!(
            &mut writer,
            r#"
  <defs>
    <!-- A marker to be used as an arrowhead -->
    <marker
      id="arrow"
      viewBox="0 0 16 16"
      refX="8"
      refY="8"
      markerWidth="9"
      markerHeight="9"
      orient="auto-start-reverse">
      <path d="M 0 0 L 16 8 L 0 16 z" stroke="none" fill="context-fill" />
    </marker>
  </defs>
            "#
        )?;

        Ok(Self {
            writer: Box::new(writer),
            level: 1,
            canvas,
        })
    }

    /// Return reference to canvas.
    pub fn canvas(&self) -> &Canvas {
        &self.canvas
    }

    fn tag_inner(tag: &str, attr: &SvgTagAttributes) -> String {
        format!(
            "{tag}{attr}",
            attr = if attr.is_empty() {
                String::new()
            } else {
                format!(" {attr}")
            }
        )
    }

    /// Write something into the SVG and consider indentation.
    pub fn with_indent(&mut self, s: &str) -> std::io::Result<()> {
        writeln!(self.writer, "{:indent$}{s}", "", indent = 2 * self.level)
    }

    /// Write a single tag `<tag>`.
    pub fn tag(&mut self, tag: &str, attr: &SvgTagAttributes) -> std::io::Result<()> {
        self.with_indent(&format!(
            "<{tag_inner}/>",
            tag_inner = Self::tag_inner(tag, attr)
        ))
    }

    /// Open a tag `<tag>`
    pub fn open_tag(&mut self, tag: &str, attr: &SvgTagAttributes) -> std::io::Result<()> {
        self.with_indent(&format!(
            "<{tag_inner}>",
            tag_inner = Self::tag_inner(tag, attr)
        ))?;

        self.level += 1;
        Ok(())
    }

    /// Close a tag `</tag>`
    pub fn close_tag(&mut self, tag: &str) -> std::io::Result<()> {
        self.level -= 1;
        self.with_indent(format!("</{tag}>").as_str())
    }

    /// Begin a new group `<g>`.
    pub fn begin_group(&mut self, attr: &SvgTagAttributes) -> std::io::Result<()> {
        self.open_tag("g", attr)
    }

    /// End a group `</g>`.
    pub fn end_group(&mut self) -> std::io::Result<()> {
        self.close_tag("g")
    }

    /// Defs tag.
    pub fn defs(&mut self, inner: &str) -> std::io::Result<()> {
        self.open_tag("defs", &Default::default())?;
        self.with_indent(inner)?;
        self.close_tag("defs")
    }

    /// Style tag.
    pub fn style(&mut self, inner: &str) -> std::io::Result<()> {
        self.open_tag("style", &Default::default())?;
        self.with_indent(inner)?;
        self.close_tag("style")
    }

    /// Finish this SVG. This method is also called in the Drop trait implementation.
    pub fn finish(&mut self) -> std::io::Result<()> {
        writeln!(self.writer, "</svg>")
    }
}

impl Drop for SvgWriter {
    fn drop(&mut self) {
        self.finish().expect("No error")
    }
}
