use crsn::asm::data::literal::Value;
use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind;
use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes, CrsnUniq};
use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{RunState};
use crsn::sexp::SourcePosition;
use std::io::{Write};
use std::convert::TryFrom;
use crsn::asm::instr::Cond;
use console::Term;

#[derive(Debug, Clone)]
pub struct StdioOps {
    hdl_stdin : Value,
    hdl_stdout : Value,
}

#[derive(Debug)]
struct StdioData {
    console: Term,
}

impl Default for StdioData {
    fn default() -> Self {
        Self {
            console : Term::stdout()
        }
    }
}

impl StdioOps {
    pub fn new() -> Box<dyn CrsnExtension> {
        Box::new(Self {
            hdl_stdin: 0,
            hdl_stdout: 0
        })
    }
}

impl CrsnExtension for StdioOps {
    fn init(&mut self, uniq: &CrsnUniq) {
        self.hdl_stdin = uniq.unique_handle();
        self.hdl_stdout = uniq.unique_handle();
    }

    /// Get value of an extension-provided constant.
    /// This constant may be an object handle, or a constant value used as argument in some other instruction.
    fn get_constant_value<'a>(&self, name: &str) -> Option<Value>
    {
        match name {
            "stdin" => Some(self.hdl_stdin),
            "stdout" => Some(self.hdl_stdout),
            _ => None
        }
    }

    fn name(&self) -> &'static str {
        "stdio"
    }

    fn parse_op<'a>(&self, _pos: &SourcePosition, _keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
        Ok(ParseRes::Unknown(args))
    }

    /// Run-time method called to read an object (using the object handle syntax)
    fn read_obj(&self, state: &mut RunState, handle: Value)
                -> Result<Option<Value>, Fault>
    {
        if handle == self.hdl_stdin {
            let data = state.ext_mut::<StdioData>();
            return Ok(Some(data.console.read_char().expect("stdin read") as u64));
        }

        if handle == self.hdl_stdout {
            return Err(Fault::NotAllowed("Cannot read stdout".into()));
        }

        Ok(None)
    }

    /// Run-time method called to write an object (using the object handle syntax)
    fn write_obj(&self, state: &mut RunState, handle: Value, value: Value) -> Result<Option<()>, Fault>
    {
        state.clear_status();

        if handle == self.hdl_stdout {
            if let Ok(a_char) = char::try_from((value & 0xFFFF_FFFF) as u32) {
                let data = state.ext_mut::<StdioData>();

                let mut b = [0; 4];
                data.console.write(a_char.encode_utf8(&mut b).as_bytes()).expect("stdout write");
            } else {
                state.set_flag(Cond::Invalid, true);
            }
            return Ok(Some(()));
        }

        if handle == self.hdl_stdin {
            return Err(Fault::NotAllowed("Cannot write stdin".into()));
        }

        Ok(None)
    }
}