// Copyright 2023-Present Jonas Kruckenberg
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use alloc::format;
use core::cell::RefCell;
use core::fmt;
use core::str::FromStr;

use anyhow::Context;
use riscv::extensions::RiscvExtensions;

use crate::arch::device;
use crate::device_tree::DeviceTree;
use crate::irq::InterruptController;

#[derive(Debug)]
pub struct Cpu {
    pub extensions: RiscvExtensions,
    pub cbop_block_size: Option<usize>,
    pub cboz_block_size: Option<usize>,
    pub cbom_block_size: Option<usize>,
    pub plic: RefCell<device::plic::Plic>,
    pub clock: kasync::time::Clock,
}

impl fmt::Display for Cpu {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "{:<17} : {}", "RISCV EXTENSIONS", self.extensions)?;
        writeln!(f, "{:<17} : {:?}", "CBOP BLOCK SIZE", self.cbop_block_size)?;
        writeln!(f, "{:<17} : {:?}", "CBOZ BLOCK SIZE", self.cboz_block_size)?;
        writeln!(f, "{:<17} : {:?}", "CBOM BLOCK SIZE", self.cbom_block_size)?;
        writeln!(f, "{:<17} : {:?}", "PLIC", self.plic)?;
        writeln!(f, "{:<17} : {}", "CLOCK", self.clock)?;

        Ok(())
    }
}

impl Cpu {
    pub fn interrupt_controller(&self) -> core::cell::RefMut<'_, dyn InterruptController> {
        core::cell::RefMut::map(self.plic.borrow_mut(), |plic| {
            plic as &mut dyn InterruptController
        })
    }

    pub fn new(devtree: &DeviceTree, hartid: usize) -> crate::Result<Self> {
        let cpus = devtree
            .find_by_path("/cpus")
            .context("device tree has no /cpus node")?;

        let cpu = cpus
            .children(devtree)
            .filter(|dev| dev.name.name == "cpu")
            .find(|dev| crate::cpu::hartid_of(devtree, *dev).is_ok_and(|id| id == hartid))
            .with_context(|| format!("device tree has no cpu node for hart {hartid}"))?;

        let cbop_block_size = cpu
            .property(devtree, "riscv,cbop-block-size")
            .map(|prop| prop.as_usize().unwrap());

        let cboz_block_size = cpu
            .property(devtree, "riscv,cboz-block-size")
            .map(|prop| prop.as_usize().unwrap());

        let cbom_block_size = cpu
            .property(devtree, "riscv,cbom-block-size")
            .map(|prop| prop.as_usize().unwrap());

        let extensions = match cpu.property(devtree, "riscv,isa-extensions") {
            Some(prop) => {
                let extensions = prop.as_strlist()?;
                parse_riscv_extensions(extensions)
            }
            None => RiscvExtensions::empty(),
        };

        // TODO find CLINT associated with this core
        let hlic_node = cpu
            .children(devtree)
            .find(|c| c.name.name == "interrupt-controller")
            .unwrap();
        tracing::trace!("CPU interrupt controller: {:?}", hlic_node);

        let plic = device::plic::Plic::new(devtree, hlic_node)?;

        let clock = device::clock::new(devtree, cpu)?;

        Ok(Self {
            clock,
            extensions,
            cbop_block_size,
            cboz_block_size,
            cbom_block_size,
            plic: RefCell::new(plic),
        })
    }

    pub const fn supports_rva20u64(&self) -> bool {
        self.extensions.contains(RiscvExtensions::RVA20U64)
    }
}

fn parse_riscv_extensions(strs: fdt::StringList) -> RiscvExtensions {
    let mut out = RiscvExtensions::empty();

    for str in strs {
        if let Ok(ext) = RiscvExtensions::from_str(str) {
            out |= ext;
        } else {
            tracing::trace!("unknown RISCV extension {str}");
        }
    }

    out
}
