Documentation
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

mod error;
mod state;

pub use crate::error::CorgError;
use crate::state::Parser;

pub struct Corg {
    delete_blocks: bool,
    warn_if_no_blocks: bool,
    omit_output: bool,
    check_only: bool,

    #[cfg(feature = "checksum")]
    checksum: bool,

    start_block_marker: String,
    end_block_marker: String,
    end_output_marker: String,
}

impl Default for Corg {
    fn default() -> Self {
        Self {
            delete_blocks: false,
            warn_if_no_blocks: false,
            omit_output: false,
            check_only: false,

            #[cfg(feature = "checksum")]
            checksum: false,

            start_block_marker: "[[[#!".to_string(),
            end_block_marker: "]]]".to_string(),
            end_output_marker: "[[[end]]]".to_string(),
        }
    }
}

impl Corg {
    #[must_use]
    pub fn delete_blocks(mut self, delete_blocks: bool) -> Self {
        self.delete_blocks = delete_blocks;
        self
    }

    #[must_use]
    pub fn warn_if_no_blocks(mut self, warn_if_no_blocks: bool) -> Self {
        self.warn_if_no_blocks = warn_if_no_blocks;
        self
    }

    #[must_use]
    pub fn omit_output(mut self, omit_output: bool) -> Self {
        self.omit_output = omit_output;
        self
    }

    #[must_use]
    pub fn check_only(mut self, check_only: bool) -> Self {
        self.check_only = check_only;
        self
    }

    #[must_use]
    #[cfg(feature = "checksum")]
    pub fn checksum(mut self, checksum: bool) -> Self {
        self.checksum = checksum;
        self
    }

    #[must_use]
    pub fn override_markers(
        mut self,
        start_block_marker: String,
        end_block_marker: String,
        end_output_marker: String,
    ) -> Self {
        self.start_block_marker = start_block_marker;
        self.end_block_marker = end_block_marker;
        self.end_output_marker = end_output_marker;
        self
    }

    pub fn execute(&self, content: &str) -> Result<String, CorgError> {
        let result = Parser::evaluate(content, self)?;
        let output = result.get_output();

        if self.warn_if_no_blocks && !result.found_blocks() {
            return Err(CorgError::NoBlocksDetected);
        }

        if self.check_only && content != output {
            return Err(CorgError::CheckFailed((
                content.to_string(),
                output.to_string(),
            )));
        }

        Ok(output.to_string())
    }
}

pub struct CorgRunner {
    input: PathBuf,
    output: Option<PathBuf>,
    replace_input: bool,
}

impl Default for CorgRunner {
    fn default() -> Self {
        Self {
            input: PathBuf::new(),
            output: None,
            replace_input: false,
        }
    }
}

impl CorgRunner {
    #[must_use]
    pub fn input(mut self, input: PathBuf) -> Self {
        self.input = input;
        self
    }

    #[must_use]
    pub fn output(mut self, output: Option<PathBuf>) -> Self {
        self.output = output;
        self
    }

    #[must_use]
    pub fn replace_input(mut self, replace_input: bool) -> Self {
        self.replace_input = replace_input;
        self
    }

    pub fn execute(&self, corg: &Corg) -> Result<String, CorgError> {
        let content = self.get_file_contents(&self.input)?;

        let output = corg.execute(&content)?;
        let out_file = if self.replace_input {
            Some(&self.input)
        } else {
            self.output.as_ref()
        };

        if let Some(out_file) = out_file {
            if let Some(parent) = out_file.parent() {
                if !parent.exists() {
                    std::fs::create_dir_all(parent)?;
                }
            }

            let mut file = File::create(out_file)?;
            file.write_all(output.as_bytes())?;
        } else {
            let mut stdout = std::io::stdout();
            stdout.write_all(output.as_bytes())?;
        };

        Ok(output)
    }

    fn get_file_contents(&self, path: &Path) -> Result<String, CorgError> {
        let mut buffer = String::new();

        if path == PathBuf::from("-") {
            std::io::stdin()
                .read_to_string(&mut buffer)
                .expect("Could not read stdin");
        } else {
            let mut file = File::open(&path)?;
            file.read_to_string(&mut buffer)?;
        }
        Ok(buffer)
    }
}