use std::fs::{write, DirBuilder};
use std::io::{Error, ErrorKind, Result};
use std::path::Path;

use crate::cluster::_Cluster;
use crate::device::_Device;
use crate::package::_Package;
use crate::peripheral::{_Peripheral, _Peripherals};
use crate::register::_Register;

pub(super) fn generate_package(project_dir: &Path, package: _Package) -> Result<()> {
    create_dir(project_dir)?;
    write(project_dir.join("Cargo.toml"), package.to_string())?;

    generate_sources(project_dir, package.device)
}

fn generate_sources(project_dir: &Path, device: _Device) -> Result<()> {
    let src_dir = project_dir.join("src");
    create_dir(&src_dir)?;

    write(src_dir.join("lib.rs"), device.to_string())?;
    generate_peripherals(&src_dir, device.peripherals)
}

fn generate_peripherals(src_dir: &Path, peripherals: _Peripherals) -> Result<()> {
    let peripherals_dir = src_dir.join("peripherals");
    create_dir(&peripherals_dir)?;

    write(peripherals_dir.join("mod.rs"), peripherals.to_string())?;
    for peripheral in peripherals {
        generate_peripheral(&peripherals_dir, peripheral)?;
    }
    Ok(())
}

fn generate_peripheral(peripherals_dir: &Path, peripheral: _Peripheral) -> Result<()> {
    let peripheral_dir = peripherals_dir.join(peripheral.name.to_string());
    create_dir(&peripheral_dir)?;

    write(peripheral_dir.join("mod.rs"), peripheral.to_string())?;
    for cluster in peripheral.clusters {
        generate_cluster(&peripheral_dir, cluster)?;
    }
    for register in peripheral.registers {
        generate_register(&peripheral_dir, register)?;
    }
    Ok(())
}

fn generate_cluster(parent_dir: &Path, cluster: _Cluster) -> Result<()> {
    let cluster_dir = parent_dir.join(cluster.name.to_string());
    create_dir(&cluster_dir)?;

    write(cluster_dir.join("mod.rs"), cluster.to_string())?;
    for cluster in cluster.clusters {
        generate_cluster(&cluster_dir, cluster)?;
    }
    for register in cluster.registers {
        generate_register(&cluster_dir, register)?;
    }
    Ok(())
}

fn generate_register(parent_dir: &Path, register: _Register) -> Result<()> {
    let register_file = parent_dir.join(format!("{}.rs", register.name.to_string()));
    write(register_file, register.to_string())
}

fn create_dir(dir: &Path) -> Result<()> {
    if dir.exists() {
        if !dir.is_dir() {
            Err(Error::new(
                ErrorKind::NotFound,
                format!("[{}] must be a directory", dir.display()),
            ))?;
        }
        Ok(())
    } else {
        DirBuilder::new().create(dir)
    }
}
