use crate::connection::*;
use crate::connection_config::*;
use crate::connection_status::*;
use crate::data_logger::*;
use std::fmt;
use std::ops::Drop;
use std::sync::{Arc, Mutex};

#[repr(C)]
#[derive(Clone, PartialEq)]
pub enum FileConverterStatus {
    InProgress,
    Complete,
}

impl fmt::Display for FileConverterStatus {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Self::InProgress => write!(formatter, "In progress"),
            Self::Complete => write!(formatter, "Complete"),
        }
    }
}

#[repr(C)]
#[derive(Clone)]
pub struct FileConverterProgress {
    pub status: FileConverterStatus,
    pub percentage: f32,
    pub bytes_processed: u64,
    pub bytes_total: u64,
}

impl fmt::Display for FileConverterProgress {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{}, {:.1}%, {} of {} bytes", self.status, self.percentage, self.bytes_processed, self.bytes_total)
    }
}

pub struct FileConverter {
    dropped: Arc<Mutex<bool>>,
}

impl FileConverter {
    pub fn new(destination: &str, name: &str, file_paths: Vec<&str>, closure: Box<dyn Fn(FileConverterProgress) + Send>) -> std::io::Result<Self> {
        let file_converter = Self {
            dropped: Arc::new(Mutex::new(false)),
        };

        let mut progress = FileConverterProgress {
            status: FileConverterStatus::InProgress,
            percentage: 0.0,
            bytes_processed: 0,
            bytes_total: file_paths.iter().map(|file_path| std::fs::metadata(file_path).map(|metadata| metadata.len())).sum::<std::io::Result<u64>>()?,
        };

        let connections: Vec<Connection> = file_paths
            .iter()
            .map(|&file_path| {
                Connection::new(&ConnectionConfig::FileConnectionConfig(FileConnectionConfig {
                    file_path: file_path.to_owned(),
                }))
            })
            .collect();

        let data_logger = DataLogger::new(&destination, &name, connections.iter().collect())?;

        connections.iter().try_for_each(|connection| connection.open())?;

        let dropped = file_converter.dropped.clone();

        std::thread::spawn(move || {
            loop {
                progress.bytes_processed = connections.iter().map(|connection| connection.get_statistics().data_total).sum();
                progress.percentage = 100.0 * ((progress.bytes_processed as f64) / (progress.bytes_total as f64)) as f32;

                if connections.iter().all(|connection| connection.get_status() == ConnectionStatus::Disconnected) {
                    break;
                }

                if let Ok(dropped) = dropped.lock() {
                    if *dropped {
                        return;
                    }
                    closure(progress.clone());
                }

                std::thread::sleep(std::time::Duration::from_millis(100));
            }

            drop(data_logger);

            progress.status = FileConverterStatus::Complete;

            if let Ok(dropped) = dropped.lock() {
                if *dropped {
                    return;
                }
                closure(progress.clone());
            }
        });

        Ok(file_converter)
    }

    pub fn convert(destination: &str, name: &str, file_paths: Vec<&str>) -> std::io::Result<()> {
        let (sender, receiver) = crossbeam::channel::unbounded();

        let closure = Box::new(move |progress| {
            sender.send(progress).ok();
        });

        let _file_converter = Self::new(destination, name, file_paths, closure)?;

        loop {
            if let Ok(progress) = receiver.recv() {
                match progress.status {
                    FileConverterStatus::InProgress => continue,
                    FileConverterStatus::Complete => return Ok(()),
                }
            }
        }
    }
}

impl Drop for FileConverter {
    fn drop(&mut self) {
        *self.dropped.lock().unwrap() = true;
    }
}