#![allow(unused)]
// #[macro_use]
// extern crate diesel;
extern crate dotenv;

use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenv::dotenv;
use std::collections::HashMap;
use std::io::{self, Read, Seek, Write};
use std::path::Path;
use std::process::Command;

use actix_identity::Identity;
use actix_multipart::Multipart;
use futures_util::TryStreamExt as _;
use serde_json::{json, Value};
use std::fs::File;
use std::process::*;
use tempfile::NamedTempFile;
use uuid::Uuid;
use actix_web::web::Data;

use std::io::prelude::*;
use std::iter::Iterator;
// use zip::result::ZipError;
// use zip::write::FileOptions;

use walkdir::{DirEntry, WalkDir};
// use reqwest::blocking::Client;

use tera::{Context, Tera};

#[derive(Debug, Clone, serde::Serialize)]
pub struct FormData {
    pub name: String,
    pub content_type: String,
    pub value: String,
    pub filename: String,
    pub path: String,
}

impl FormData {
    pub fn new(_name: String, _content_type: String) -> Self {
        FormData {
            name: _name,
            content_type: _content_type,
            value: "".to_string(),
            filename: "".to_string(),
            path: "".to_string(),
        }
    }

    pub fn save_file<P: AsRef<Path>>(self, path: P) {
        let content = String::from_utf8_lossy(&std::fs::read(&self.path).unwrap()).to_string();
        
        if content != "" {
            std::fs::create_dir_all(&path.as_ref().parent().unwrap()).unwrap();
            std::fs::rename(self.path, &path.as_ref());
        }
    }
}

// enum FormDataVec {
//     FormData(FormData),
//     FormDataVec(Vec<FormData>),
// }

pub async fn get_form_data(mut payload: Multipart) -> HashMap<String, FormData> {
    let mut form_datas: HashMap<String, FormData> = HashMap::new();
    let mut is_finish = false;
    while !is_finish {
        let mut field_result = &mut payload.try_next().await;
        if field_result.is_ok() {
            if let Some(field) = field_result.as_mut().unwrap() {
                let mut form_data =
                    FormData::new(field.name().to_string(), field.content_type().to_string());
                let content_disposition = field.content_disposition();
                let filename_option = content_disposition.get_filename();
                if filename_option.is_some() {
                    form_data.filename = filename_option.unwrap().to_string();
                    let mut f = NamedTempFile::new().unwrap();
                    form_data.path = f.into_temp_path().to_str().unwrap().to_string();
                    let mut file = std::fs::File::create(form_data.path.clone()).unwrap();
                    while let Some(chunk) = field.try_next().await.unwrap() {
                        file.write_all(&chunk);
                    }
                } else {
                    let mut value = String::new();
                    while let Some(chunk) = field.try_next().await.unwrap() {
                        let str_chunk = std::str::from_utf8(&chunk).unwrap();
                        value.push_str(str_chunk);
                    }
                    form_data.value = value;
                    // field.get_result()
                }
                form_datas.insert(form_data.name.clone(), form_data);
            } else {
                is_finish = true;
            }
        } else {
            is_finish = true;
            println!("{:?}", "this form is not enctype=multipart/form-data");
        }
    }
    return form_datas;
}

pub fn generate_key() -> String {
    use rand::Rng;
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789";
    const PASSWORD_LEN: usize = 10;
    let mut rng = rand::thread_rng();

    let password: String = (0..PASSWORD_LEN)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    return password;
}

// pub fn get_user_session(id: &Identity) -> Result<User, String> {
//     if let Some(user_id) = id.identity() {
//         let result = User::find(user_id.parse::<i32>().unwrap());
//         return result;
//     }
//     Err("Not cookie found".to_string())
// }

pub async fn convert_video<P: AsRef<Path>, Q: AsRef<Path>>(
    from: P,
    to: Q,
    data: &Value,
    quality: &'static str,
) {
    let mut buffer: String;
    let mut to_folder = to.as_ref().parent().unwrap();
    let stdout_path = to_folder.join(format!(
        "{}{}",
        to.as_ref().file_name().unwrap().to_str().unwrap(),
        "_stdout.txt"
    ));
    let data_path = to_folder.join(format!(
        "{}{}",
        to.as_ref().file_name().unwrap().to_str().unwrap(),
        "_data.json"
    ));
    std::fs::write(&data_path, data.to_string());
    let mut final_name = to.as_ref().as_os_str().to_str().unwrap();
    // let mut str = final_name.to_string();
    // str = str.replace(".mp4", format!("{}.mp4", quality).as_str());
    // final_name = str.as_str();

    println!(
        "converting {:?} to {:?}",
        from.as_ref().as_os_str().to_str().unwrap(),
        &final_name
    );
    let mut cmd = Command::new("./src/soft/ffmpeg");
    cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .args([
            "-y",
            "-progress",
            "-",
            "-i",
            from.as_ref().as_os_str().to_str().unwrap(),
            "-vcodec",
            "libx264",
            "-acodec",
            "aac",
            "-scodec",
            "mov_text",
            "-map",
            "V",
            "-map",
            "a?",
            "-map",
            "s?",
        ]);
    if quality != "copy" {
        cmd.args(["-s", &quality]);
    }
    cmd.arg(final_name);
    let mut ffmpeg = cmd.spawn().expect("failed to execute child");
    ffmpeg.wait();
    std::fs::remove_file(&stdout_path);
    std::fs::write(&data_path, get_video_data(&to).unwrap().to_string());
    println!(
        "converting end {:?} to {:?}",
        from.as_ref().as_os_str().to_str().unwrap(),
        to.as_ref().as_os_str().to_str().unwrap()
    );
}

pub fn convert_e57_las<P: AsRef<Path>>(path: P) -> Result<String, String> {
    let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
    let mut cmd = Command::new("wine"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
    cmd.stdout(Stdio::null())
        .stderr(Stdio::from(File::create(&stdout_path).unwrap()))
        .stdin(Stdio::null())
        .args([
        "./src/soft/e572las.exe",
        "-v",
        "-i",
        format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
        "-o",
        format!("{}.las", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
    ]);
    println!("{:?}", cmd);

    let mut ffmpeg = cmd.spawn().expect("failed to execute wine e572las");
    ffmpeg.wait();
    std::fs::remove_file(&stdout_path);
    // let mut output = cmd.output().expect("failed to execute wine e572las");
    // let result = String::from_utf8_lossy(&output.stderr).to_string();
    println!("convert_e57_las");
    Ok("result".to_string())
}

pub fn convert_las_potree<P: AsRef<Path>>(path: P) -> Result<String, String> {
    let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
    let mut cmd = Command::new("./src/e57extractor/PotreeConverter"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
    cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .args([
        // "./src/e57extractor/PotreeConverter.exe",
        format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
    ]);
    println!("{:?}", cmd);

    let mut output = cmd.output().expect("failed to execute PotreeConverter");
    let fs = std::fs::File::create(
        format!(
            "{}_converted/sources.json",
            path.as_ref().as_os_str().to_str().unwrap()
        )
        .as_str(),
    );
    let mut ffmpeg = cmd.spawn().expect("failed to execute PotreeConverter");
    ffmpeg.wait();
    std::fs::remove_file(&stdout_path);
    // let mut output = cmd.output().expect("failed to execute PotreeConverter");
    // let result = String::from_utf8_lossy(&output.stdout).to_string();
    println!("convert_las_potree");
    Ok("result".to_string())
}

pub fn convert_e57_images<P: AsRef<Path>>(path: P) -> Result<String, String> {
    let stdout_path = path.as_ref().parent().unwrap().join("cmd_logs.txt");
    let mut cmd = Command::new("python3"); // wine ./src/soft/e572las.exe -v -i \"{e57filepath}\" -o \"{e57filepath}.las\
    cmd.stdout(Stdio::from(File::create(&stdout_path).unwrap()))
        .stderr(Stdio::from(File::create(&stdout_path).unwrap()))
        .stdin(Stdio::null())
        .args([
        "./src/e57extractor/",
        format!("{}", path.as_ref().as_os_str().to_str().unwrap()).as_str(),
        "--only-images",
    ]);
    println!("{:?}", cmd);

    let mut ffmpeg = cmd.spawn().expect("failed to execute PotreeConverter");
    ffmpeg.wait();

    let output = String::from_utf8_lossy(&std::fs::read(&stdout_path).unwrap()).to_string();
    std::fs::remove_file(&stdout_path);
    if output.contains("File contains no 2D images. Exiting...") || output.contains("E57_ERROR") {
        println!("No image found");
        return Err("No image found".to_string());
    }
    println!("convert_e57_images");
    println!("{}", output);
    Ok("result".to_string())
}

pub fn get_video_data<P: AsRef<Path>>(path: P) -> Result<Value, serde_json::Error> {
    let mut output = Command::new("./src/soft/ffprobe")
        .args([
            "-print_format",
            "json",
            "-show_format",
            "-show_streams",
            "-show_chapters",
            path.as_ref().as_os_str().to_str().unwrap(),
        ])
        .output()
        .expect("failed to execute ffprobe");
    let str = String::from_utf8_lossy(&output.stdout).to_string();
    let result: Result<Value, serde_json::Error> = serde_json::from_str(&str);
    result
}

pub fn save_file_field(field: &FormData) -> String {
    let extension = Path::new(&field.filename)
        .extension()
        .unwrap()
        .to_str()
        .unwrap()
        .to_string();
    let uuid = Uuid::new_v4().to_string();
    let final_path_local =
        Path::new("./static/sourcefiles").join(format!("{}.{}", uuid, extension));
    let final_path = Path::new("/static/sourcefiles")
        .join(format!("{}.{}", uuid, extension))
        .to_str()
        .unwrap()
        .to_string();

    std::fs::create_dir_all("./static/sourcefiles");
    std::fs::rename(&field.path, &final_path_local);

    return final_path;
}

pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
    std::fs::create_dir_all(&dst)?;
    for entry in std::fs::read_dir(src)? {
        let entry = entry?;
        let ty = entry.file_type()?;
        if ty.is_dir() {
            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
        } else {
            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
        }
    }
    Ok(())
}
