// Copyright (C) 2024 Philipp Hofer
//
// Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
// the European Commission - subsequent versions of the EUPL (the "Licence").
// You may not use this work except in compliance with the Licence.
//
// You should have received a copy of the European Union Public License along
// with this program.  If not, you may obtain a copy of the Licence at:
// <https://joinup.ec.europa.eu/software/page/eupl>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Licence is distributed on an "AS IS" basis,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the Licence for the specific language governing permissions and
// limitations under the Licence.

//! Represents a parsed law text.

use serde::{Deserialize, Serialize};
use std::{
    cell::RefCell,
    fmt::{self, Display},
    path::Path,
    rc::Rc,
    sync::Arc,
};
use tracing::instrument;

use crate::{config::Config, misc::Error, overview::parse};

pub mod responsible;

/// That's our struct, holding all the information of the law text.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Law {
    pub name: String,
    pub header: Vec<Heading>,
}

impl Law {
    #[instrument]
    /// Creates a `Law` instance from a configuration file.
    ///
    /// This function initializes the law processing pipeline by loading configurations from the
    /// specified path, parsing law texts based on these configurations, and assembling a `Law`
    /// object. The configuration file is expected to define how law texts should be parsed and
    /// processed.
    ///
    /// # Parameters
    ///
    /// - `path`: A path to the configuration file. This can be any type that implements the
    /// `AsRef<Path>` trait, allowing for flexible path specifications (e.g., `&str`, `String`,
    /// `Path`, or `PathBuf`).
    ///
    /// # Returns
    ///
    /// Returns a `Result` that, on success, contains the `Law` constructed from the provided
    /// configuration and parsed law texts. On failure, it returns an `Error` indicating what went
    /// wrong during the process.
    ///
    /// # Errors
    ///
    /// This function can return an `Error` in the following scenarios:
    ///
    /// - If loading the configuration from the specified path fails (e.g., file not found,
    /// permission issues, or invalid file format). - If the parsing of law texts encounters an
    /// error or if the construction of the `Law` object based on the parsed data fails.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use risp::law::Law;
    ///
    /// let law = Law::from_config("./data/configs/abgb.toml").unwrap();
    /// ```
    pub fn from_config<P: AsRef<Path> + tracing::Value + std::fmt::Debug>(
        path: P,
    ) -> Result<Law, Error> {
        let (law_id, mut builder, parser) = Config::load(path)?;
        let pars = parse(law_id)?;

        for par in pars {
            let cont = parser.parse(&par, &mut builder)?;
            if !cont {
                break;
            }
        }

        Ok(builder.into())
    }

    //TODO: add test
    pub fn to_md(&self) {
        for header in &self.header {
            Self::print_md(header, 2);
        }
    }

    fn print_md(header: &Heading, level: usize) {
        println!("{} {}", "#".repeat(level), header);
        for content in &header.content {
            match content {
                HeadingContent::Heading(h) => {
                    Self::print_md(h, level + 1);
                }
                HeadingContent::Paragraph(p) => {
                    println!("{} {p}", "#".repeat(level + 1));
                }
            }
        }
    }
}

impl From<Builder> for Law {
    fn from(builder: Builder) -> Self {
        let mut ret = Vec::new();

        for header in builder.header {
            ret.push(Heading {
                name: header.borrow().name.clone(),
                desc: header.borrow().desc.clone(),
                content: header.borrow().clone().into(),
            });
        }

        Self {
            header: ret,
            name: builder.name,
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Heading {
    pub name: String, //1. Hauptstück; 3. Theil; ...
    pub desc: Option<String>,
    pub content: Vec<HeadingContent>, // 1. Theil; 1. Subtheil; ...
}

impl Display for Heading {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(desc) = &self.desc {
            f.write_str(&format!("{} ({desc})\n", self.name))
        } else {
            f.write_str(&format!("{}\n", self.name))
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum HeadingContent {
    Paragraph(Section),
    Heading(Heading),
}

impl From<ClassifierInstance> for Vec<HeadingContent> {
    fn from(value: ClassifierInstance) -> Vec<HeadingContent> {
        let mut ret = Vec::new();

        for subsection in value.sections {
            ret.push(HeadingContent::Paragraph(subsection));
        }

        for child in value.children {
            ret.push(HeadingContent::Heading(Heading {
                name: child.borrow().name.clone(),
                desc: child.borrow().desc.clone(),
                content: child.borrow().clone().into(),
            }));
        }

        ret
    }
}

/// A `Builder` struct for constructing a `Law` structure.
///
/// This struct is designed to facilitate the building of a `Law`'s structure, primarily organized by
/// a set of classifiers. Each classifier represents a hierarchical level within the law, such as
/// chapters, sections, or articles. The builder pattern is used to incrementally construct the
/// law, allowing for the addition of headers, paragraphs, and notes in a structured manner.
#[derive(Debug)]
pub struct Builder {
    name: String,

    /// Structure of the law text
    classifiers: Vec<Classifier>,

    /// Instances
    header: Vec<Rc<RefCell<ClassifierInstance>>>,

    last_instance: Option<Rc<RefCell<ClassifierInstance>>>,

    /// Stores the header of the next paragraph
    next_para_header: Option<String>,

    next_para_note: Option<String>,

    #[cfg(test)]
    pub history: Vec<String>,
}

impl PartialEq for Builder {
    fn eq(&self, other: &Self) -> bool {
        self.classifiers == other.classifiers
            && self.header == other.header
            && self.next_para_header == other.next_para_header
    }
}

impl Default for Builder {
    fn default() -> Self {
        Self::new(String::new())
    }
}

impl Builder {
    #[must_use]
    /// Creates a new law builder. Adds classifier for known law texts.
    pub fn new(name: String) -> Self {
        Self {
            name,
            classifiers: Vec::new(),
            header: Vec::new(),
            next_para_header: None,
            last_instance: None,
            next_para_note: None,
            #[cfg(test)]
            history: Vec::new(),
        }
    }

    pub fn add_classifier(&mut self, classifier: Classifier) {
        self.classifiers.push(classifier);
    }

    fn responsible_classifier(&self, name: &str) -> Option<&Classifier> {
        self.classifiers.iter().find(|&c| c.used_for(name))
    }

    fn find_parent(
        &self,
        cur: Option<Rc<RefCell<ClassifierInstance>>>,
        class: &Classifier,
    ) -> Option<Rc<RefCell<ClassifierInstance>>> {
        let mut cur = cur;
        while let Some(c) = cur {
            let (cur_name, cur_parent) = {
                let c_borrow = c.borrow();
                (c_borrow.name.clone(), c_borrow.parent.clone())
            };

            let cur_responsible_class = self.responsible_classifier(&cur_name).unwrap();
            if cur_responsible_class == class {
                return c.borrow_mut().get_parent();
            }

            cur = cur_parent;
        }
        None
    }

    /// Adds a new heading to the law structure.
    ///
    /// This method is used to introduce a new heading into the law's structure, effectively
    /// updating the law's organization and hierarchy. It determines the appropriate classifier for
    /// the new heading and updates the internal state to reflect the addition of this heading. If
    /// the new heading has a responsible classifier, it is either added as a new root heading or
    /// as a child to an existing heading, based on the hierarchy defined by the classifiers.
    ///
    /// # Parameters
    ///
    /// - `name`: A string slice representing the name of the new heading. The name is trimmed of
    /// any leading or trailing whitespace before processing.
    ///
    /// # Behavior
    ///
    /// - The function logs the addition of the new heading for informational purposes.
    /// - It attempts to find a responsible classifier for the given heading name. If no such
    ///   classifier is found, the program will panic, indicating a configuration or logic error.
    /// - Depending on the law's current structure and the heading's designated classifier, the
    ///   heading is either added as a new root heading, as a child to the last added heading, or
    ///   as a child to a specific parent heading determined by the law's hierarchy.
    ///
    /// # Panics
    ///
    /// Panics if no responsible classifier can be found for the provided heading name. This panic
    /// serves as a direct alert to the user, indicating that the law texts' configuration likely
    /// lacks a necessary classifier for the given heading.
    ///
    /// # Examples
    ///
    /// ```
    /// use risp::{Config, law::{Law, Heading}};
    /// use std::path::Path;
    ///
    /// let (_, mut builder, _) = Config::load(Path::new("data/configs/abgb.toml")).unwrap();
    ///
    /// builder.new_header("1. Theil");
    ///
    /// let law: Law = builder.into();
    /// assert_eq!(
    ///     law,
    ///     Law {
    ///         name: "ABGB".into(),
    ///         header: vec![Heading {
    ///             name: "1. Theil".into(),
    ///             desc: None,
    ///             content: vec![]
    ///         }]
    ///     }
    /// );
    /// ```
    pub fn new_header(&mut self, name: &str) {
        let name = name.trim();
        #[cfg(test)]
        self.history.push(format!("New_header: {name}"));

        let responsible_class = self
            .responsible_classifier(name)
            .unwrap_or_else(|| panic!("No classifier for '{name}'"));

        let mut heading: ClassifierInstance = name.into();

        if let Some(last_instance) = &self.last_instance {
            let cur = Some(last_instance.clone());

            let parent = &self.find_parent(cur, responsible_class);
            match parent {
                None => {
                    if responsible_class.root {
                        let c = Rc::new(RefCell::new(heading));
                        self.header.push(c.clone());
                        self.last_instance = Some(c.clone());
                    } else {
                        heading.set_parent(last_instance.clone());
                        let c = Rc::new(RefCell::new(heading));
                        last_instance.borrow_mut().add_child(c.clone());
                        self.last_instance = Some(c.clone());
                    }
                }
                Some(parent) => {
                    heading.set_parent(parent.clone());
                    let c = Rc::new(RefCell::new(heading));
                    parent.borrow_mut().add_child(c.clone());
                    self.last_instance = Some(c.clone());
                }
            }
        } else {
            let c = Rc::new(RefCell::new(heading));
            self.header.push(c.clone());
            self.last_instance = Some(c.clone());
        }
    }

    /// Sets a new description for the last classifier in the law structure.
    ///
    /// This method updates the description of the most recently added classifier. It's useful for
    /// providing additional context or details about the classifier's role or content within the
    /// law's hierarchy.
    ///
    /// # Parameters
    ///
    /// - `desc`: A string slice representing the new description to be assigned to the last
    /// classifier. The description is trimmed of any leading or trailing whitespace before being
    /// set.
    ///
    /// # Behavior
    ///
    /// - The method trims the input description. - It then attempts to update the description of
    /// the last classifier. If no classifier has been added before this method is called, the
    /// function will panic, indicating that setting a description requires a preceding classifier
    /// to be in place.
    ///
    /// # Panics
    ///
    /// Panics if there is no last classifier available when the method is called. This situation
    /// occurs if an attempt is made to set a description of a classifier before any classifier has
    /// been added to the structure, highlighting a logical error in the usage sequence.
    ///
    /// # Examples
    /// ```
    /// use risp::{Config, law::{Law, Heading}};
    /// use std::path::Path;
    ///
    /// let (_, mut builder, _) = Config::load(Path::new("data/configs/abgb.toml")).unwrap();
    ///
    /// builder.new_header("1. Theil");
    /// builder.new_desc("Description of the first header");
    ///
    /// let law: Law = builder.into();
    /// assert_eq!(
    ///     law,
    ///     Law {
    ///         name: "ABGB".into(),
    ///         header: vec![Heading {
    ///             name: "1. Theil".into(),
    ///             desc: Some("Description of the first header".into()),
    ///             content: vec![]
    ///         }]
    ///     }
    /// );
    /// ```
    pub fn new_desc(&mut self, desc: &str) {
        let desc = desc.trim();
        #[cfg(test)]
        self.history.push(format!("New desc: {desc}"));

        self.last_instance
            .clone()
            .expect("We can only set a description, if we already have received a classifier.")
            .borrow_mut()
            .set_desc(desc);
    }

    /// Adds a new paragraph to the current section of the law.
    ///
    /// This method is responsible for introducing a new paragraph into the law's structure,
    /// encapsulating both the paragraph's symbol (e.g. '§ 1') and its content. It also handles
    /// optional paragraph headers and notes that may precede the paragraph, ensuring these are
    /// correctly associated with the paragraph.
    ///
    /// # Parameters
    ///
    /// - `par`: A `String` representing the symbol or identifier of the new paragraph (e.g. '§
    ///   1'). This is typically a unique reference within the context of the law or section.
    /// - `content`: The `Content` of the paragraph, encapsulating the actual text or provisions
    ///   contained within the paragraph.
    ///
    /// # Panics
    ///
    /// Panics if there is no current classifier available to associate the new paragraph with.
    /// This scenario suggests a logical error in the sequence of operations, as paragraphs should
    /// only be added within the context of a defined classifier. It indicates the need for a
    /// classifier to be defined before paragraphs can be added.
    ///
    /// # Examples
    ///
    /// Assuming `law` is a mutable reference to an instance of a law struct:
    ///
    /// ```
    /// use risp::{Config, law::{Law, Heading, Content, HeadingContent, Section}};
    /// use std::path::Path;
    ///
    /// let (_, mut builder, _) = Config::load(Path::new("data/configs/abgb.toml")).unwrap();
    /// builder.new_header("1. Theil");
    ///
    /// let par = "§ 1".to_string();
    /// let content = Content::Text("The content of Article 1.".into());
    /// builder.new_par(par, content);
    ///
    /// let law: Law = builder.into();
    /// assert_eq!(
    ///     law,
    ///     Law {
    ///         name: "ABGB".into(),
    ///         header: vec![Heading {
    ///             name: "1. Theil".into(),
    ///             desc: None,
    ///             content: vec![HeadingContent::Paragraph(Section {
    ///                 symb: "§ 1".into(),
    ///                 par_header: None,
    ///                 par_note: None,
    ///                 content: Content::Text("The content of Article 1.".into())
    ///             })]
    ///         }]
    ///     }
    /// );
    /// ```
    pub fn new_par(&mut self, par: String, content: Content) {
        #[cfg(test)]
        self.history.push(format!(
            "New_par: {par};{}",
            serde_json::to_string(&content).unwrap()
        ));

        let par_header = self.next_para_header.clone();
        self.next_para_header = None;

        let par_note = self.next_para_note.clone();
        self.next_para_note = None;

        self.last_instance
            .clone()
            .expect("Expect at least one classifier")
            .borrow_mut()
            .add_section(Section {
                symb: par,
                par_header,
                par_note,
                content,
            });
    }

    /// Next paragraph has a header, store its name.
    pub fn new_next_para_header(&mut self, header: &str) {
        #[cfg(test)]
        self.history.push(format!("New_new_para_header: {header}"));

        if let Some(next_para_header) = &self.next_para_header {
            self.new_header(&next_para_header.clone()); // promote to bigger header :-)
        }

        self.next_para_header = Some(header.trim().into());
    }

    pub fn add_next_para_note(&mut self, content: String) {
        #[cfg(test)]
        self.history.push(format!("New_para_note: {content}"));

        self.next_para_note = Some(content);
    }
}

#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct Section {
    pub symb: String, // §"1", §"2", ...
    pub par_header: Option<String>,
    pub par_note: Option<String>,
    pub content: Content,
}

impl fmt::Debug for Section {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let par_header = self.par_header.as_deref().unwrap_or("");
        write!(f, "{} ({})\n{:#?}", self.symb, par_header, self.content)
    }
}

impl fmt::Display for Section {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut to_write = if let Some(header) = &self.par_header {
            format!("{} ({})\n{}", self.symb, header, self.content)
        } else {
            format!("{}\n{}", self.symb, self.content)
        };
        if let Some(note) = &self.par_note {
            to_write.push_str(&format!("\nBeachte: {note}\n"));
        }

        f.write_str(&to_write)
    }
}

#[derive(Clone, PartialEq)]
struct ClassifierInstance {
    name: String, //e.g. 1 Theilstück
    desc: Option<String>,
    sections: Vec<Section>,
    children: Vec<Rc<RefCell<ClassifierInstance>>>,
    parent: Option<Rc<RefCell<ClassifierInstance>>>,
}

impl ClassifierInstance {
    fn new(name: &str) -> Self {
        Self {
            name: name.into(),
            desc: None,
            parent: None,
            sections: Vec::new(),
            children: Vec::new(),
        }
    }

    fn set_parent(&mut self, parent: Rc<RefCell<ClassifierInstance>>) {
        self.parent = Some(parent);
    }

    fn get_parent(&self) -> Option<Rc<RefCell<ClassifierInstance>>> {
        self.parent.clone()
    }

    fn add_child(&mut self, child: Rc<RefCell<ClassifierInstance>>) {
        self.children.push(child);
    }

    fn set_desc(&mut self, desc: &str) {
        self.desc = Some(desc.into());
    }

    fn add_section(&mut self, section: Section) {
        self.sections.push(section);
    }
}

impl std::fmt::Debug for ClassifierInstance {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Classifier")
            .field("name", &self.name)
            .field("desc", &self.desc)
            .field("sections", &self.sections)
            .field("children", &self.children)
            .finish_non_exhaustive()
    }
}

impl From<&str> for ClassifierInstance {
    fn from(value: &str) -> Self {
        Self::new(value)
    }
}

pub(crate) type ClassifierApplicable = Arc<dyn Fn(&str, &str) -> bool>;

#[derive(Clone)]
/// Represents a structural element within a legal document, such as a chapter, section, or
/// article.
///
/// A `Classifier` is used to categorize parts of a legal document into hierarchical levels, aiding
/// in the organization and navigation of the content. It can represent various structural elements
/// like chapters, sections, articles, etc., depending on the specific needs of the legal document
/// being processed.
///
/// # Examples
///
/// ```
/// use std::{sync::Arc, rc::Rc, cell::RefCell};
/// use risp::law::Classifier;
///
/// fn starts_with(classifier_name: &str, title: &str) -> bool {
///     title.starts_with(classifier_name)
/// }
///
/// let chapter_classifier = Classifier::new("Chapter", Arc::new(starts_with));
///
/// let chapter_classifier = chapter_classifier.root();
/// ```
pub struct Classifier {
    /// The name or title of the classifier (e.g., "Hauptstück", "Theil", "Abschnitt").
    pub name: String,

    pub(crate) used_for_fn: ClassifierApplicable,

    instances: Vec<ClassifierInstance>,

    /// Child classifiers. This enables a hierarchical structure where classifiers can contain
    /// nested levels of other classifiers.
    pub child: Vec<Rc<RefCell<Classifier>>>,

    /// Specifies whether this classifier is a root element of the document structure. Root
    /// classifiers are typically top-level elements like major divisions or parts of a document.
    pub root: bool,
}

impl PartialEq for Classifier {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl Classifier {
    /// Constructs a new `Classifier` with the specified name and applicability function.
    pub fn new(name: &str, used_for_fn: ClassifierApplicable) -> Self {
        Self {
            name: name.into(),
            used_for_fn,
            child: Vec::new(),
            instances: Vec::new(),
            root: false,
        }
    }

    #[must_use]
    /// Marks the classifier as a root element, returning a modified instance of the classifier.
    pub fn root(self) -> Self {
        Self { root: true, ..self }
    }

    fn used_for(&self, name: &str) -> bool {
        (self.used_for_fn)(&self.name, name)
    }
}

impl std::fmt::Debug for Classifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Classifier")
            .field("name", &self.name)
            .field("instances", &self.instances)
            .field("child", &self.child)
            .finish_non_exhaustive()
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Content {
    Text(String),       //This is my direct law text
    List(Vec<Content>), //(1) This is general law. (2) This is more specific law
    Multi(Vec<Content>),
}

impl Display for Content {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Text(a) => f.write_str(&format!("{a}\n")),
            Self::List(a) | Self::Multi(a) => {
                let mut ret = String::new();
                for aa in a {
                    ret.push_str(&format!("{aa}"));
                }
                f.write_str(&ret)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::fs;

    use crate::{config::Config, law::Content};

    use super::Law;

    #[test]
    fn allow_both_paragraphs_and_headers_in_same_header() {
        let configs = fs::read_dir("./data/configs").expect("No folder with config files");

        for config in configs {
            let path = format!("{}", config.unwrap().path().display());
            if path.contains("abgb") {
                let (_law_id, mut builder, _) = Config::load(&path).unwrap();
                builder.new_header("Nullter Theil. Einleitung");
                builder.new_par("§ 1.".into(), Content::Text("My test law text.".into()));
                builder.new_header("Erster Hauptstück. Einleitung");
                builder.new_par("§ 252.".into(), Content::Text("My test law text.".into()));

                let law: Law = builder.into();

                let text = format!("{law:#?}");

                assert!(text.contains(" 252."));
            }
        }
    }
}
