/**
 * Copyright (C) 2023 awk4j - https://ja.osdn.net/projects/awk4j/
 * <p>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::io::Result;
use std::io::{BufReader, BufWriter};
// use std::io::{Error, Result};
use std::path::{Path, PathBuf};
// use std::string::FromUtf8Error;

/**
 * Path <--> PathBuf
// let path: &Path = &path_buf;
// let path_buf: PathBuf = path.to_path_buf();
 */

/**
 * New path - dir, foo/var --> dir/var
 */
pub fn new_path(dir: &str, file: &str) -> String {
    let path: &Path = Path::new(file); // ../foo/var
    let name: String = get_filename(path); // var
    let path_buf: PathBuf = PathBuf::from(dir).join(name); // dir/var
    let x: &Path = &path_buf;
    path_to_string(x)
}

/**
 * Path -> String
 */
pub fn path_to_string(path: &Path) -> String {
    match path.to_str() {
        None => panic!("Path is not a valid UTF-8 sequence"),
        Some(x) => x.to_string().replace("\\", "/"),
    }
}

/**
 * get file name
 */
pub fn get_filename(path: &Path) -> String {
    let x = path.file_name().unwrap().to_string_lossy();
    x.to_string()
}

/**
 * mkdir path
 */
pub fn mkdir(path: &str) -> () {
    let p = Path::new(path);
    if !p.is_dir() {
        match fs::create_dir(p) {
            Err(e) => panic!("{}", error("mkdir", path, e.to_string())),
            Ok(_) => (), // 指定されたパスが見つかりません
        }
    }
    ()
}

/**
 * copy from to -> length
 */
pub fn copy(from: &str, to: &str) -> u64 {
    match fs::copy(from, to) {
        Err(e) => panic!("{}", error("I/O Error", from, e.to_string())),
        Ok(len) => len, // 指定されたファイルが見つかりません
    }
}

/**
 * Open reader
 */
pub fn open_reader(path: &str) -> (FD, BufReader<File>) {
    let file: File = match File::open(path) {
        Err(e) => panic!("{}", error("Read Error", path, e.to_string())),
        Ok(x) => x,
    };
    let reader: BufReader<File> = BufReader::new(file);
    let fd = FD {
        path: path.to_string().to_owned(),
        buf: String::new(),
        line_number: 0,
        eof: false,
    };
    (fd, reader)
}
// Read line
pub fn read(mut fd: FD, mut reader: BufReader<File>) -> (FD, BufReader<File>) {
    let mut buf = String::with_capacity(BUF_CAPACITY);
    let mut len: usize = 0;
    if !fd.eof {
        let rs = reader.read_line(&mut buf);
        len = match rs {
            Err(e) => panic!("{}", error("Read Error", &fd.path, e.to_string())),
            Ok(x) => x,
        };
        if len > 0 {
            fd.line_number += 1;
        }
    }
    fd.buf = buf.trim_end().to_string();
    fd.eof = len == 0; // 通常は少なくとも \n がある
    (fd, reader)
}
const BUF_CAPACITY: usize = 192;

/**
 * Open writer
 */
pub fn open_writer(path: &str) -> (FD, BufWriter<File>) {
    let file: File = match File::create(path) {
        Err(e) => panic!("{}", error("Write Error", path, e.to_string())),
        Ok(x) => x,
    };
    let writer: BufWriter<File> = BufWriter::new(file);
    let fd = FD {
        path: path.to_string().to_owned(),
        buf: String::new(),
        line_number: 0,
        eof: false, // unused
    };
    (fd, writer)
}
// Write line
pub fn write(mut fd: FD, mut writer: BufWriter<File>) -> (FD, BufWriter<File>) {
    let rs: Result<_> = write!(writer, "{}\n", fd.get_buf());
    let _rs = match rs {
        Err(e) => panic!("{}", error("Write Error", &fd.path, e.to_string())),
        Ok(x) => x,
    };
    fd.line_number += 1;
    (fd, writer)
}

// File descriptor (Read/Write) - 構造体、クローン可能
#[derive(Debug, Clone)]
pub struct FD {
    pub path: String,     // used in error messages
    pub buf: String,      // Read/Write
    pub line_number: u32, // Number of rows processed
    pub eof: bool,        // Read
}
impl FD {
    pub fn get_buf(&self) -> String {
        self.buf.clone()
    }
}

// Test Reader
pub fn _reader_test01(path: &str) {
    let (mut rfd, mut reader) = open_reader(path);
    while !rfd.eof {
        let (_rfd, _reader) = read(rfd, reader);
        reader = _reader;
        rfd = _rfd;
        if !rfd.eof {
            println!(". {}", rfd.get_buf());
        }
    }
}
// Test Reader/Writer
pub fn _writer_test01(input: &str, output: &str) -> u32 {
    let (mut rfd, mut reader) = open_reader(input);
    let (mut wfd, mut writer) = open_writer(output);
    let mut lines: u32 = 0;
    loop {
        let (_rfd, _reader) = read(rfd, reader);
        reader = _reader;
        rfd = _rfd.clone();
        if _rfd.eof {
            break;
        }
        wfd.buf = rfd.get_buf();
        let (_wfd, _writer) = write(wfd.clone(), writer);
        writer = _writer;
        lines += 1;
    }
    let _ = writer.flush();
    lines
}
// Test
#[test]
fn path_test() {
    let path: &Path = Path::new(_TEST);
    let sp: String = path_to_string(path);
    assert_eq!(_TEST, sp);
    println!("--> Path-->String: {}", sp);
}

const _TEST: &str = "../foo/var";
const _IN: &str = "SAMPLE.html";
const _OUT: &str = "~SAMPLE.html";
const _ERR: &str = "ERROR.html";

pub fn run() {
    // path_test();
    // _reader_test01(_IN);
    // let lines = _writer_test01(_IN, _OUT);
}

/**
 * Error
 */
const RESET: &str = "\x1b[m"; // [0m
const RED: &str = "\x1b[91m"; // error
const _GREEN: &str = "\x1b[92m"; // debug
const _YELLOW: &str = "\x1b[93m"; // title
const BLUE: &str = "\x1b[94m"; // options
const _MAGENTA: &str = "\x1b[95m"; // warning
const _CYAN: &str = "\x1b[96m"; // information

// To RED
pub fn red(msg: String) -> String {
    format!("{}{}{}", RED, msg, RESET)
}
// To BLUE
pub fn blue(msg: &str) -> String {
    format!("{}{}{}", BLUE, msg, RESET)
}

// Error message
fn error(msg: &str, file: &str, e: String) -> String {
    format!("{}: {:?} {}", red(msg.to_string()), file, red(e))
}

// Make escape sequence
// fn mk_escape(esc: u8, target: &str) -> String {
//     let mut _arr: Vec<u8> = format!("%{}", target).to_string().into();
//     _arr[0] = esc;
//     let rs = String::from_utf8(_arr);
//     match rs {
//         Ok(ok) => ok,
//         Err(e) => panic!("from_utf8: {}", red(e.to_string())),
//     }
// }

/*
.as_str();  // String -> &str型
*/
