use clap::{Parser};

pub mod asotr_data {
    use std::{fs::File, fs::read_to_string, io::Read};
    use byteorder::{LittleEndian, ReadBytesExt};
    use chrono::{DateTime, Utc};
    use std::time::{SystemTime, UNIX_EPOCH, Duration};
    use strum::FromRepr;
    use lazy_static::lazy_static;
    use regex::Regex;
    use walkdir::WalkDir;

    lazy_static! {
        pub static ref support_dtypes: String = 
            String::from(".data01.asotr01(02), data02.asotr01(02), data06.asotr01(02)");

        pub static ref patterns_fnames_csv_data: Vec<(String, String)> = {
            let mut patterns: Vec<(String, String)> = Vec::new();
            patterns.push((String::from(".*data01.asotr01"), 
                           String::from("../data/asotr/asotr01_data_T.csv")));
            patterns.push((String::from(".*data02.asotr01"), 
                           String::from("../data/asotr/asotr01_data_P.csv")));
            patterns.push((String::from(".*data06.asotr01"), 
                           String::from("../data/asotr/asotr01_data_TSET.csv")));
            patterns.push((String::from(".*data01.asotr02"), 
                           String::from("../data/asotr/asotr02_data_T.csv")));
            patterns.push((String::from(".*data02.asotr02"), 
                           String::from("../data/asotr/asotr02_data_P.csv")));
            patterns.push((String::from(".*data06.asotr02"), 
                           String::from("../data/asotr/asotr02_data_TSET.csv")));

            patterns
        };

        pub static ref patterns_disp: Vec<String> = {
            let mut patterns: Vec<String> = Vec::new();
            patterns.push(String::from("ASOTR01 temperature"));
            patterns.push(String::from("ASOTR01 power"));
            patterns.push(String::from("ASOTR01 temperature setpoint"));
            patterns.push(String::from("ASOTR02 temperature"));
            patterns.push(String::from("ASOTR02 power"));
            patterns.push(String::from("ASOTR02 temperature setpoint"));
            patterns
        };
    }

    #[derive(Debug, FromRepr, PartialEq)]
    enum AsotrDataType {
        Temp = 1,
        Pow = 2,
        TempSet = 6,
    }

    struct AsotrDataDesc {
        time_s: u64,
        time_mks: u32,
        date: String,
        time: String,
        data_type: AsotrDataType,
    }

    impl AsotrDataDesc {
        pub fn new(time_s: u64, time_mks: u32, date: String, time: String, 
                   data_type: AsotrDataType) -> AsotrDataDesc {
            AsotrDataDesc { time_s, time_mks, date, time, data_type }
        }
    } 

    pub fn read_data(filename_full: String) -> Result<String, String> {
        let ch_u16: [u16; 6];
        let ch_f32: [f32; 6];

        let asotr_head = parse_filename(filename_full.clone())?;

        let mut buf = Vec::new();
        let mut out = String::new();

        let mut data = match File::open(filename_full.clone())
        {
            Ok(file) => file,
            Err(msg) => { return Err(format!("Error opening data file {}: {}", filename_full, msg)) }
        };

        match data.read_to_end(&mut buf) {
            Ok(stat) => stat,
            Err(msg) => { return Err(format!("Error reading data file {}: {}", filename_full, msg)) }
        };

        out.push_str(&format!("{};{} {}.{:02};", 
                              &asotr_head.time_s,
                              &asotr_head.date,
                              &asotr_head.time, 
                              asotr_head.time_mks));

        if asotr_head.data_type == AsotrDataType::Temp || 
            asotr_head.data_type == AsotrDataType::TempSet {
                ch_f32 = parse_data_f32(buf)?;
                for elem in ch_f32 { 
                    out.push_str(&elem.to_string());  
                    out.push(';');
                }
            }
        else if asotr_head.data_type == AsotrDataType::Pow {
            ch_u16 = parse_data_u16(buf)?;
            for elem in ch_u16 { 
                out.push_str(&elem.to_string());  
                out.push(';');
            }
        }

        out.remove(out.len() - 1);
        return Ok(out);
    }


    pub fn read_csv_data_(fname: &str) -> Result<Vec<String>, String> {
        match read_to_string(fname) {
            Ok(content) => {
                let mut result = Vec::new();
                for line in content.lines().skip(1) {
                    result.push(line.to_string())
                }
                Ok(result)
            }
            Err(err) => Err(err.to_string())
        }
    }

    pub fn parse_data_dir(dir: &str, disp: bool) -> Result<(), String> {
        let mut data: Vec<String> = Vec::new();

        println!("parse data from directory: {}", dir);
        for (i, (pattern, fname)) in patterns_fnames_csv_data.iter().enumerate() {
            let mut data1 = read_csv_data_(fname)?;
            let files = find_files_regex(dir, pattern)?;

            data.append(&mut data1);

            for elem in files {
                data.push(read_data(elem)?);
            }

            data.sort();
            data.dedup();

            if disp { disp_data(&data, &patterns_disp[i])?; }

            println!("save csv data to file: {}", fname);

            save_data_csv(data.clone(), fname)?;
            data.clear();
        }

        return Ok(());
    }

    fn parse_data_f32(buf: Vec<u8>) -> Result<[f32; 6], String> {
        let mut data = &buf[..];
        let mut ch: [f32; 6] = [0.0; 6];

        for i in 0..6 {
            ch[i] = match data.read_f32::<LittleEndian>() {
                Ok(val) => val,
                Err(msg) => { 
                    return Err(format!(
                            "Error parsing file: failed parsing float32 data: {}", msg)); }
            }
        }

        return Ok(ch);
    }

    fn parse_data_u16(buf: Vec<u8>) -> Result<[u16; 6], String> {
        let mut data = &buf[..];
        let mut ch: [u16; 6] = [0; 6];

        for i in 0..6 {
            ch[i] = match data.read_u16::<LittleEndian>() {
                Ok(val) => val,
                Err(msg) => { 
                    return Err(format!(
                            "Error parsing file: failed parsing uint16 data: {}", msg)); }
            }
        }

        return Ok(ch);
    }

    fn parse_filename(filename_full: String) -> Result<AsotrDataDesc, String> {
        let mut fname = String::new();
        let msg_prev = format!("Error parsing filename {}:", filename_full);

        match filename_full.rfind('/').or_else(|| filename_full.rfind('\\')) {
            Some(val) => { fname = (filename_full[val+1..filename_full.len()]).to_string(); }
            _ => { fname = filename_full.clone();  }
        }

        if fname.len() != 32 { 
            return Err(format!("{} unsupported file", msg_prev)); 
        }

        let time_unix_ = fname[0..10].parse::<u64>();
        let time_unix = match &time_unix_ {
            Ok(data) => data,
            Err(msg) => { 
                return Err(format!("{} expected digits in timestamp sec part ({})", 
                                   msg_prev, msg));  
            }
        };

        let data_type_ = fname[22..24].parse::<u8>();
        let data_type_u8 = match &data_type_ {
            Ok(data) => data,
            Err(msg) => { 
                return Err(format!("{} expected digits in data type part ({})", 
                                   msg_prev, msg));  
            }
        };

        if *data_type_u8 == 1 || *data_type_u8 == 2 || *data_type_u8 == 6 { }
        else {
            return Err(format!("{} parser supports data types: {}", 
                               msg_prev, support_dtypes.to_string()));
        }

        let data_type = match AsotrDataType::from_repr(*data_type_u8 as usize) {
            Some(value) => value,
            _ => return Err(format!("{} expected digits in data type part", 
                                    msg_prev))
        };

        let _time_str_mks = fname[11..14].parse::<u32>();
        let time_mks = match &_time_str_mks {
            Ok(data) => data,
            Err(msg) => { return Err(format!("{}: expected digits in timestamp mks part ({})", 
                                             msg_prev, msg));  }
        };

        let time: SystemTime = UNIX_EPOCH + Duration::from_secs(*time_unix);
        let date_time = DateTime::<Utc>::from(time);
        let date_s = date_time.format("%d.%m.%Y").to_string();
        let time_s = date_time.format("%H:%M:%S").to_string();

        let head = AsotrDataDesc::new(*time_unix, *time_mks, date_s, time_s, data_type);

        return Ok(head);
    }

    fn find_files_regex(dir: &str, template: &str) -> Result<Vec<String>, String> {
        let mut path_vec: Vec<String> = Vec::new();

        let regex = match Regex::new(template) {
            Ok(val) => val,
            Err(msg) => { 
                return Err(format!("Error create regex template ({}): {}",
                template, msg)); 
            }
        };

        let data = WalkDir::new(dir)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| regex.is_match(e.file_name().to_str().unwrap()))
            .map(|e| e.into_path());

        for elem in data {
            path_vec.push(elem.display().to_string());
        }

        if path_vec.len() == 0 { 
            return Err(format!(
                    "Error searching files for pattern ({}): files not found in directory {}", 
                    template, dir));  
        }

        return Ok(path_vec);
    }

    fn disp_data(data: &Vec<String>, about: &str) -> Result<(), String> {
        println!("{}", about);
        println!("{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", 
                 "timestamp_sec", "timestamp", 
                 "ch1", "ch2", "ch3", "ch4", "ch5", "ch6");

        for elem in data {
            println!("{}", elem.replace(';', "\t"));
        }

        return Ok(())
    }

    fn save_data_csv(data: Vec<String>, fname: &str) -> Result<(), String> {

        let mut data_w: Vec<String> = Vec::new();
        data_w.push(format!("{};{};{};{};{};{};{};{}", 
                            "timestamp_sec", "timestamp", 
                            "ch1", "ch2", "ch3", "ch4", "ch5", "ch6"));

        for elem in data {
            data_w.push(elem);
        }

        match std::fs::write(fname, data_w.join("\n")) {
            Ok(f) => f,
            Err(msg) => { 
                return Err(format!("Error write data to csv file {}: {}", fname, msg)) 
            }
        }

        return Ok(());
    }

    
}

#[derive(Parser, Debug)]
#[command(version, author, about, long_about = None)]
struct Cli {
    /// file with ASOTR MVN data (.data01.asotr01(02), data02.asotr01(02), data06.asotr01(02))
    #[arg(long, short = 'f', value_name = "FILE_DATA")]
    filename: Option<String>,

    /// directory with ASOTR MVN data
    #[arg(long, short = 'd', value_name = "DIRECTORY_DATA")]
    directory: Option<String>,

    /// show data in console
    #[arg(short = 's')]
    show: bool,
}


fn main() {
    use crate::asotr_data::*; 

    let cli = Cli::parse();
    let show = cli.show;

    if let Some(fname) = &cli.filename {

        let s = match read_data(fname.clone()) {
            Ok(elem) => elem,
            Err(msg) => { println!("{}", msg); return; } 
        };

        println!("{}", s);

        return;
    }

    if let Some(dir) = &cli.directory {
        match parse_data_dir(&dir.clone(), show) {
            Ok(elem) => elem,
            Err(msg) => { println!("{}", msg); return; }
        } 
        return;
    }

    println!("Unexpected command. Type --help for more iformation");
}






