use regex::Regex;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use std::io::{BufReader, BufRead, Write};
use std::net::{TcpListener, TcpStream};
use clap::{Parser};

pub mod perf {
    use chrono::{DateTime, Utc, Local};

    pub struct Perf {
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        delta_ms: i64,
        delta_mks: i64,
        delta_ns: i64,
    }

    impl Perf  {
        pub fn new() -> Perf {
            Perf {
                start: Utc::now(),
                end: Utc::now(),
                delta_ms: 0,
                delta_mks: 0,
                delta_ns: 0,
            }
        }

        pub fn start(&mut self) {
            self.start = Utc::now();
        }

        // measure: "ms" or "mks"
        pub fn stop(&mut self, out_msg: &str, measure: &str) {
            self.end = Utc::now();
            self.delta_ms = (self.end - self.start).num_microseconds().unwrap()/1000;
            self.delta_mks = (self.end - self.start).num_microseconds().unwrap();
            self.delta_ns = (self.end - self.start).num_nanoseconds().unwrap();

            if measure == "mks" { println!("{} (mks): {:?}", out_msg, self.delta_mks); }
            else if measure == "ns" { println!("{} (ns): {:?}", out_msg, self.delta_ns); }
            else { println!("{} (ms): {:?}", out_msg, self.delta_ms); }
        }

        pub fn stop_restart(&mut self, out_msg: &str, measure: &str) {
            self.stop(out_msg, measure);
            self.start = Utc::now();
        }

        pub fn cur_time(format: &str) -> String {
            let now = Local::now();
            let fmt = now.format(format).to_string();
            return fmt;
        }

    }
}

use crate::perf::Perf;


pub mod uart_transact {
    const MAX_BUF: usize = 4096;
    use serialport::{SerialPort, StopBits, Parity, FlowControl, DataBits};
    use std::time::Duration;
    use crate::perf::Perf;
    use std::io::{self, Write, BufWriter, BufReader, BufRead};
    use std::fs::{File, OpenOptions};
    use std::path::Path;

    #[derive(PartialEq)]
    pub enum TypeEn {
        Rx = 0,
        Tx,
        RxTx,
        TxRx,
    }

    #[derive(Debug, Clone)]
    pub struct PortSettingsT {
        baud_rate: u32,
        data_bits: DataBits,
        parity: Parity,
        stop_bits: StopBits,
        flow_ctrl: FlowControl,
        timeout_msec: u64,
    }

    impl PortSettingsT {
        pub fn new(baud_rate: u32, data_bits_: u8, parity_: &str, stop_bits_: u8,
                   flow_ctrl_: &str, timeout_msec: u64) -> Result<Self, String> {

            let data_bits = match data_bits_ {
                5 => DataBits::Five,
                6 => DataBits::Six,
                7 => DataBits::Seven,
                8 => DataBits::Eight,
                _ => { return Err(format!(
                            "error: the number of data bits specified is incorrect 
                            (type from 5 to 8)")); 
                }
            };

            let parity = match parity_ {
                "Even" => Parity::Even,
                "Odd" => Parity::Odd,
                "None" => Parity::None,
                _ => { return Err(format!(
                            "error: the parity of data specified is incorrect 
                            (type \"Even\", \"Odd\" or \"None\")")); 
                }
            };

            let stop_bits = match stop_bits_ {
                1 => StopBits::One,
                2 => StopBits::Two,
                _ => { return Err(format!(
                            "error: the stop bits of data specified is incorrect 
                            (type 1 or 2)")); 
                }
            };

            let flow_ctrl = match flow_ctrl_ {
                "None" => FlowControl::None,
                "Software" => FlowControl::Software,
                "Hardware" => FlowControl::Hardware,
                _ => { return Err(format!(
                            "error: the parity of data specified is incorrect 
                            (type \"None\", \"Software\" or \"Hardware\")")); 
                }
            };

            Ok(Self { baud_rate, data_bits, parity, stop_bits, flow_ctrl, timeout_msec })
        }
    }

    struct Logger {
        out: BufWriter<File>, 
        log_flag: bool,
    }

    impl Logger {
        fn new<P: AsRef<Path>>(filename: P, log_flag: bool) -> Result<Self, io::Error> {
            let path = Path::new(filename.as_ref());

            let file = match path.exists() {
                true => OpenOptions::new().write(true).append(true).open(filename.as_ref())?,
                false => {
                    println!("path: {:?}", path);
                    File::create(filename.as_ref())?
                }, 
            };

            let out = BufWriter::new(file);

            Ok(Self { out, log_flag })
        }

        fn log(&mut self, msg: &str) -> Result<(), io::Error> {
            if self.log_flag == true {
                writeln!(&mut self.out, "{} {};{:?}", 
                         Perf::cur_time("%Y.%m.%d"), 
                         Perf::cur_time("%H:%M:%S.%3f"),
                         msg.trim())?;
                self.out.flush()?;
            }
            Ok(())
        }

        fn log_cmd_data(&mut self, msg_cmd: &str, msg_data: &str) -> Result<(), io::Error> {
            if self.log_flag == true {
                writeln!(&mut self.out, "{} {};{:?};{:?}",
                         Perf::cur_time("%Y.%m.%d"), 
                         Perf::cur_time("%H:%M:%S.%3f"),
                         msg_cmd.trim(),
                         msg_data.trim())?;
                self.out.flush()?;
            }
            Ok(())
        }
    }

    pub struct UartTransact {
        type_: TypeEn,
        port_name: String,
        port_settings: PortSettingsT,
        tx_packet: String,
        rx_packet: String,

        pub port: Box<dyn SerialPort>, 
        save_data_flag: bool,
        save_cmd_flag: bool,
        data_writer: Logger,
        cmd_writer: Logger,
        cmd_data_writer: Logger,
        specific_fname: String
    }

    impl UartTransact {
        pub fn new(trans_type: &str, 
                   port_name: &str, 
                   port_settings: PortSettingsT,
                   save_data_flag: bool,
                   save_cmd_flag: bool,
                   specific_fname: &str) -> Result<Self, io::Error> {

            let mut type_ = TypeEn::TxRx;
            if trans_type == "TxRx" { type_ = TypeEn::TxRx;  }
            else if trans_type == "Tx" { type_ = TypeEn::Rx;  }
            else if trans_type == "RxTx" { type_ = TypeEn::RxTx;  }
            else if trans_type == "Rx" { type_ = TypeEn::Rx;  }

            let mut filename = format!("../log/data_{}_{}.log", 
                                       Perf::cur_time("%Y%m%d"),
                                       specific_fname); 
            let data_writer = Logger::new(filename.clone(), save_data_flag)?; 
            filename = format!("../log/cmd_{}_{}.log", 
                               Perf::cur_time("%Y%m%d"),
                               specific_fname); 
            let cmd_writer = Logger::new(filename.clone(), save_data_flag)?; 
            filename = format!("../log/cmd_data_{}_{}.log", 
                               Perf::cur_time("%Y%m%d"),
                               specific_fname); 
            let cmd_data_writer = Logger::new(filename.clone(), save_data_flag)?;

            let port = Self::open_port_settings(port_name, 
                                                   port_settings.clone()).unwrap();

            // println!("port: {:#?}", port);
            let tx_packet = String::new();
            let rx_packet = String::new();

            Ok( Self { type_, 
                port_name: port_name.to_string(), 
                port_settings, 
                save_data_flag, 
                save_cmd_flag, 
                data_writer, 
                cmd_writer,
                cmd_data_writer,
                port, 
                tx_packet, 
                rx_packet,
                specific_fname: specific_fname.to_string(),
            })
        }


        pub fn add_packet_for_tx(&mut self, buf: String, size: u32) -> Result<(), String> {
            if size as usize >= MAX_BUF {
                return Err(format!("too much data for transmit! Data size must be < {} bytes", MAX_BUF));
            }
            else { self.tx_packet = buf; }

            return Ok(());
        }

        pub fn start_transact(&mut self, type_: &str) -> 
            Result<(), String> {

            if type_ == "txRx"  {
                self.cmd_writer.log(&self.tx_packet).unwrap();

                match self.port.write(&self.tx_packet.as_bytes()) {
                    Ok(_) => {},
                    Err(e) => eprintln!("Error sending data: {}", e),
                }

                self.port.flush().unwrap();

                let mut reader = BufReader::new(&mut self.port);
                let mut s = String::new();
                if let Err(err) = reader.read_line(&mut s) {
                    let msg = format!("serial port error: {}", err);
                    self.rx_packet = msg.clone();
                    self.data_writer.log(&msg).unwrap();
                    self.cmd_data_writer.log_cmd_data(&self.tx_packet, &msg).unwrap();
                    return Ok(());
                }

                if s.len() > 0 {
                    self.rx_packet = s.clone();
                    self.data_writer.log(&s).unwrap();
                    self.cmd_data_writer.log_cmd_data(&self.tx_packet, &s).unwrap();
                }
            }
            else if type_ == "tx" {
                self.cmd_writer.log(&self.tx_packet).unwrap();
                self.cmd_data_writer.log_cmd_data(&self.tx_packet, 
                                                  "").unwrap();

                match self.port.write(&self.tx_packet.as_bytes()) {
                    Ok(_) => {},
                    Err(e) => eprintln!("Error sending data: {}", e),
                }

                self.port.flush().unwrap();
            }
            Ok(())
        }

        pub fn get_received_packet(&mut self) -> String {
            self.rx_packet.clone()
        }

        fn open_port_settings(port_name: &str, 
                             set: PortSettingsT) -> Result<Box<dyn SerialPort>, String> {

            let mut port = match serialport::new(port_name, set.baud_rate)
                .timeout(Duration::from_millis(set.timeout_msec)).open() {
                    Ok(p) => p,
                    Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
                };

            match port.set_baud_rate(set.baud_rate) {
                Ok(_) => {},
                Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
            }
            match port.set_data_bits(set.data_bits) {
                Ok(_) => {},
                Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
            }
            match port.set_parity(set.parity) {
                Ok(_) => {},
                Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
            }
            match port.set_stop_bits(set.stop_bits) {
                Ok(_) => {},
                Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
            }
            match port.set_flow_control(set.flow_ctrl) {
                Ok(_) => {},
                Err(msg) => return Err(format!("error: failed to open serial port {} ({})", port_name, msg)),
            }

            return Ok(port);
        }
    }
}

use crate::uart_transact::*;

fn read_str(s: &str) -> Result<Vec<String>, String> { 
    let re = match Regex::new(r#""([^"]+)"|(\S+)"#) {
        Ok(val) => val,
        Err(_) => { return Err(format!("Error in regex!"));  }
    };

    let res: Vec<String> = re.find_iter(&s)
        .map(|match_| match_.as_str().to_owned().replace("\"", ""))
        .collect();

    Ok(res)
}

fn uart_cmd_interp(cmd: &str, uart_trans: &mut UartTransact) -> String {
    let mut out = String::new();

    let args = match read_str(&cmd) {
        Ok(arg) => arg,
        Err(msg) => { 
            return format!("error: failed read input string: {}", msg);
        }
    };

    let args_cnt = args.len();
    if args_cnt == 0 { return format!("{}", out); }
    let cmd_head = &args[0].trim();

    if *cmd_head == "exit" { return format!("exit"); }
    else if cmd_head.contains("help") {
        out.push_str(&format!("Serial port terminal supports the following commands:"));
        out.push_str(&format!("\n1. exit - \t\texcecute quit from this program"));
        out.push_str(&format!("\n2. help - \t\texplain how to use this program"));
        out.push_str(&format!("\n3. show_ports - \tshows all available serial ports"));
        out.push_str(&format!("\n4. send - \t\tsends specified <data> to serial port\n\tUse: send <transaction mode: txRx or tx> <data>\n\tExample: send txRx \"command data1 data2\""));
        out.push_str(&format!("\n5. open_port - open serial port with specified settings\n \tUse: open_port <port_name> <speed> <data_bits> <parity> <stop_bits> <flow_ctrl> <timeout> <transact_type> <save_data_flags> <specific filename>\n \tWhere: \n\t\t<port name> - port name \n\t\t<speed> - speed (9600 for example) \n\t\t<data bits>: 5, 6, 7, 8 \n\t\t<parity>: None, Even, Odd \n\t\t<stop bits>: 1, 2 \n\t\t<flow ctrl>: None, Software, Hardware \n\t\t<timeout> - timeout for receive and transmit in msec \n\t\t<transaction type>: tx, rx, TxRx, RxTx (tx - transmit, rx - receive) \n\t\t<save_data_flags>: true, false \n\t\t<specific filename>: specify a specific string that will be added to the filename for logging commands and data \n\tExample: open_port /dev/ttyUSB0 9600 8 Even 1 None 1000 TxRx true device1\n"));
        return out;
    }
    else if *cmd_head == "show_ports" {
        match serialport::available_ports() {
            Ok(ports) => {
                for port in ports {
                    out.push_str(&format!("{}: {:?}\n", port.port_name, port.port_type));
                }
            }
            Err(msg) => { return format!("error while get all available serial port list: {}", msg); }
        }

        return out;
    }
    else if *cmd_head == "send" {
        if args_cnt != 3 { return format!("Error while parse command! {}", "arguments count must be 2"); }
        else {
            let transact_type = &args[1];
            // let mut perf = Perf::new();
            // perf.start();
            // println!("port_set: {:#?}", uart_trans.port);

            let mut data_out = args[2].clone();
            data_out.push_str(std::str::from_utf8(b"\x0D\x0A").unwrap()); // CR + LF

            // perf.stop_restart("time for push_str: ", "mks");
            // println!("data out: {:?}", data_out);
            uart_trans.add_packet_for_tx(data_out.clone(), data_out.len() as u32).unwrap();
            // perf.stop_restart("time for add packet for tx: ", "mks");
            uart_trans.start_transact(&transact_type).unwrap();
            // perf.stop_restart("time for transaction: ", "mks");

            let mut data = "".to_string();

            if transact_type == "txRx" {
                let s = uart_trans.get_received_packet();
                data = format!("{}: {}", Perf::cur_time("%H:%M:%S.%3f"), s.trim());

            }
            
            return data;
        }
    }
    else if *cmd_head == "open_port" {

        if args_cnt != 11 { return format!("Error while parse command! {}", "arguments count must be 10"); }
        else {
            let port_name = &args[1];
            let speed = args[2].parse::<u32>().unwrap();
            let data_bits = args[3].parse::<u8>().unwrap();
            let parity = &args[4];
            let stop_bits = args[5].parse::<u8>().unwrap();
            let flow_ctrl = &args[6];
            let timeout = args[7].parse::<u64>().unwrap();
            let trans_type = &args[8];
            let save_data_flag = args[9].parse::<bool>().unwrap(); 
            let save_cmd_flag = save_data_flag; 
            let specific_fname = &args[10];

            let port_set = PortSettingsT::new(speed, data_bits, parity, stop_bits, 
                                               flow_ctrl, timeout).unwrap();
            
            // println!("port_set: {:#?}", port_set);
            *uart_trans = UartTransact::new(trans_type, 
                                           &port_name, 
                                           port_set.clone(), 
                                           save_data_flag, 
                                           save_cmd_flag,
                                           &specific_fname).unwrap(); 

            let data = format!("{}: port {} was set with the following settings: {:?}", 
                               Perf::cur_time("%H:%M:%S.%3f"), 
                               port_name,
                               port_set);
            return data;
        }
    }
    else {
        return "unknown command!".to_string();
    }

}


fn cli_mode(uart_trans: &mut UartTransact) {
    let fname_hist = "../settings/cmd_history.log".to_string();
    let mut editor = Editor::<()>::new();
    if editor.load_history(&fname_hist).is_err() {
        println!("error while reading cmd_history.log file!", ); 
        return;
    } 

    loop {
        let s: String = match editor.readline(">> ") {
            Ok(s_) => s_.to_string(),
            Err(ReadlineError::Interrupted) => {
                editor.save_history(&fname_hist)
                    .expect("error while save the command to the history file (cmd_history.log)"); 
                return; 
            },
            Err(ReadlineError::Eof) => {
                editor.save_history(&fname_hist) 
                    .expect("error while save the command to the history file (cmd_history.log)"); 
                return; 
            },
            Err(msg) => {
                println!("{}{:?}",
                         "unknown command",
                         msg);
                return;
            }
        };

        editor.add_history_entry(&s);

        let reply = uart_cmd_interp(&s, uart_trans);

        if reply == "exit" { break; }
        else { println!("{}", reply); }

    }

    editor.save_history(&fname_hist).unwrap();
}

fn tcp_server(uart_transact: &mut UartTransact, addr: &str) {
    let listener = TcpListener::bind(addr).unwrap();
    println!("TCP-server is running on address: {}", addr);

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                println!("connect to client: {:?}", stream);
                handle_client(stream, uart_transact);

            },
            Err(ref msg) => {
                println!("error: failed attempt to set connection with client: ({:?}): {}", 
                         stream, msg);
            }
        }
    }
}

fn handle_client(mut stream: TcpStream, uart_transact: &mut UartTransact) {
    loop {
        // let mut perf = Perf::new();
        // perf.start();
        let mut reader = BufReader::new(&stream);
        let mut rx_data = String::new();
        if let Err(msg) = reader.read_line(&mut rx_data) {
            println!("error: failed attempt to read data from client: {}", msg);
            return;
        }

        // println!("rx_data: ({})", rx_data.trim());
        if rx_data.starts_with("send") || 
            rx_data.starts_with("help") || 
                rx_data.starts_with("open_port") || 
                rx_data.starts_with("show_ports") {
                    let reply = uart_cmd_interp(&rx_data.trim(), uart_transact);

                    // reply.push('\n');
                    if rx_data.starts_with("send") ||
                        rx_data.starts_with("open_port") {
                            println!("rx_uart: ({})", reply.trim());
                        }
                    if let Err(msg) = stream.write_all(reply.as_bytes()) {
                        println!("error: failed attempt to send data to client: {}", msg);
                        return;
                    }
                }
        else if rx_data.starts_with("exit") {
            println!("The client requested to close the connection"); 
            return;

        }
        else if rx_data == "" {
            let data = format!("empty string! ");

            if let Err(msg) = stream.write_all(data.as_bytes()) {
                println!("error: failed attempt to send data to client: {}", msg);
                return;
            }
            return;
        }
        else {
            let data = format!("unsuppoted command: ({})\n", rx_data.trim());

            if let Err(msg) = stream.write_all(data.as_bytes()) {
                println!("error: failed attempt to send data to client: {}", msg);
                return;
            }
        }
        // perf.stop("", "mks");
    }
}

#[derive(Parser, Debug)]
#[command(version, author, about, long_about = None)]
struct Cli {
    /// working mode: 
    /// tcp:ip:port - remote control with TCP/IP, or cli - command line interface. 
    /// Example1: <uart_server> -m tcp:127.0.0.1:10000.    
    /// Example2: <uart_server> -m cli
    #[arg(long, short = 'm', value_name = "MODE")]
    mode: Option<String>, 

    /// port name: type serial port name in order to start working
    #[arg(long, short = 'p', value_name = "PORT_NAME")]
    port_name: Option<String>,
}

fn main() {

    let cli = Cli::parse();

    let mut port_name = "/dev/ttyUSB1".to_string();
    let port_set = PortSettingsT::new(9600, 8, "None", 1, "None", 1000).unwrap();

    if let Some(port) = &cli.port_name { port_name = port.to_string(); }
    else
    { println!("error: incorrect parameter: PORT_NAME. Type --help"); return; }
    
    let mut uart_trans = UartTransact::new("TxRx", &port_name, 
                                           port_set.clone(), 
                                           false, 
                                           false,
                                           "data").unwrap(); 

    if let Some(mode) = &cli.mode {
        if mode.contains("cli") { cli_mode(&mut uart_trans); }
        else if mode.contains("tcp") { tcp_server(&mut uart_trans, &mode[4..]);  }
        else { println!("error: incorrect parameter: MODE. type --help");  }
    }

}

// fn print_type_of<T>(_: &T) {
//     println!("{}", std::any::type_name::<T>());
// }


// perf.stop("time for transaction", "mks");

// let mut buffer = Vec::new();
// match port.read_to_end(&mut buffer) {
//     Ok(_) => {
//         if buffer.len() > 0 {
//             let data = format!("{}: {:?}", cur_time("%H:%M:%S"), buffer);
//             println!("{}", data);
//             if save_data_flag == true {
//                 writeln!(data_writer, "{}", 
//                          format!("{};{};{}", 
//                          cur_time("%Y.%m.%d"), 
//                          cur_time("%H:%M:%S"), 
//                          String::from_utf8(buffer).expect("msg")))
//                     .expect("err");
//             }
//         }
//     },
//     Err(msg) => eprintln!("{}", msg),
// }

// let mut cnt: u32 = 0;
// let mut i = 0;
// let mut buf: String = String::new();

// let start = Utc::now(); 
// let mut timeout_flag = false;

// let mut delta1 = 0;
// while cnt == 0 {
//     cnt = match port.bytes_to_read() {
//         Ok(c) => c,
//         Err(msg) => { println!("{}", msg); return; }
//     };
//     let delta = Utc::now() - start;
//     delta1 =  delta.num_microseconds().unwrap()/1000;
//     if delta1 > timeout_ms {
//         println!("timeout: {}", delta1);
//         timeout_flag = true;
//         break;
//     };
// }

// println!("time (ms): {}", delta1);

// // println!("time (ms): {:?}", delta.num_microseconds().unwrap()/1000);

// if timeout_flag == false {
//     while i < 500000 {
//         // thread::sleep(time::Duration::from_millis(10));
//         i += 1;
//         let mut buffer = [0u8; 4096];

//         cnt = match port.bytes_to_read() {
//             Ok(c) => c,
//             Err(msg) => { println!("{}", msg); return; }
//         };

//         if cnt > 0 {
//             match port.read(&mut buffer) {
//                 Ok(count) => {
//                     let s = std::str::from_utf8(&buffer[..count]).unwrap();
//                     buf.push_str(s);
//                     println!("{}", s);
//                 },

//                 Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { println!("timeout!"); },
//                 Err(e) => eprintln!("error reading from port ({}): {}", port_name, e),
//             }
//         }
//         else {}
//     }

// }


// if buf.len() > 0 {
//     let data = format!("{}: {:?}", cur_time("%H:%M:%S"), buf);
//     println!("{}", data);
//     if save_data_flag == true {
//         writeln!(data_writer, "{};{};{}", cur_time("%Y.%m.%d"), cur_time("%H:%M:%S"), buf.trim()).expect("err");
//     }
// }

