// Allow unused imports while developing
#![allow(unused_imports, dead_code)]

use crate::{compiler::SinglepassCompiler, machine::AssemblyComment};
use std::{
    collections::HashMap,
    fs::File,
    io::{self, Write},
    num::NonZero,
    path::PathBuf,
    sync::Arc,
};
use target_lexicon::Architecture;
use wasmer_compiler::{
    Compiler, CompilerConfig, Engine, EngineBuilder, ModuleMiddleware,
    misc::{CompiledKind, function_kind_to_filename, save_assembly_to_file},
};
use wasmer_types::{
    Features,
    target::{CpuFeature, Target},
};

/// Callbacks to the different Cranelift compilation phases.
#[derive(Debug, Clone)]
pub struct SinglepassCallbacks {
    debug_dir: PathBuf,
}

impl SinglepassCallbacks {
    /// Creates a new instance of `SinglepassCallbacks` with the specified debug directory.
    pub fn new(debug_dir: PathBuf) -> Result<Self, io::Error> {
        // Create the debug dir in case it doesn't exist
        std::fs::create_dir_all(&debug_dir)?;
        Ok(Self { debug_dir })
    }

    fn base_path(&self, module_hash: &Option<String>) -> PathBuf {
        let mut path = self.debug_dir.clone();
        if let Some(hash) = module_hash {
            path.push(hash);
        }
        std::fs::create_dir_all(&path)
            .unwrap_or_else(|_| panic!("cannot create debug directory: {}", path.display()));
        path
    }

    /// Writes the object file memory buffer to a debug file.
    pub fn obj_memory_buffer(
        &self,
        kind: &CompiledKind,
        module_hash: &Option<String>,
        mem_buffer: &[u8],
    ) {
        let mut path = self.base_path(module_hash);
        path.push(function_kind_to_filename(kind, ".o"));
        let mut file =
            File::create(path).expect("Error while creating debug file from Cranelift object");
        file.write_all(mem_buffer).unwrap();
    }

    /// Writes the assembly memory buffer to a debug file.
    pub fn asm_memory_buffer(
        &self,
        kind: &CompiledKind,
        module_hash: &Option<String>,
        arch: Architecture,
        mem_buffer: &[u8],
        assembly_comments: HashMap<usize, AssemblyComment>,
    ) -> Result<(), wasmer_types::CompileError> {
        let mut path = self.base_path(module_hash);
        path.push(function_kind_to_filename(kind, ".s"));
        save_assembly_to_file(arch, path, mem_buffer, assembly_comments)
    }
}

#[derive(Debug, Clone)]
pub struct Singlepass {
    pub(crate) enable_nan_canonicalization: bool,

    /// The middleware chain.
    pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,

    pub(crate) callbacks: Option<SinglepassCallbacks>,

    /// The number of threads to use for compilation.
    pub num_threads: NonZero<usize>,
}

impl Singlepass {
    /// Creates a new configuration object with the default configuration
    /// specified.
    pub fn new() -> Self {
        Self {
            enable_nan_canonicalization: true,
            middlewares: vec![],
            callbacks: None,
            num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
        }
    }

    pub fn canonicalize_nans(&mut self, enable: bool) -> &mut Self {
        self.enable_nan_canonicalization = enable;
        self
    }

    /// Callbacks that will triggered in the different compilation
    /// phases in Singlepass.
    pub fn callbacks(&mut self, callbacks: Option<SinglepassCallbacks>) -> &mut Self {
        self.callbacks = callbacks;
        self
    }

    /// Set the number of threads to use for compilation.
    pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
        self.num_threads = num_threads;
        self
    }
}

impl CompilerConfig for Singlepass {
    fn enable_pic(&mut self) {
        // Do nothing, since singlepass already emits
        // PIC code.
    }

    /// Transform it into the compiler
    fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
        Box::new(SinglepassCompiler::new(*self))
    }

    /// Gets the supported features for this compiler in the given target
    fn supported_features_for_target(&self, _target: &Target) -> Features {
        Features::default()
    }

    /// Pushes a middleware onto the back of the middleware chain.
    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
        self.middlewares.push(middleware);
    }
}

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

impl From<Singlepass> for Engine {
    fn from(config: Singlepass) -> Self {
        EngineBuilder::new(config).engine()
    }
}
