use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use parking_lot::Mutex;

use Error;

/// Completion status, returned by `ProgressCounter::complete`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Completion {
    /// Loading is complete
    Complete,
    /// Some asset loads have failed
    Failed,
    /// Still loading assets
    Loading,
}

/// The `Progress` trait, allowing to track which assets are
/// imported already.
pub trait Progress {
    /// The tracker this progress can create.
    type Tracker: Tracker;

    /// Add `num` assets to the progress.
    /// This should be done whenever a new asset is
    /// put in the queue.
    fn add_assets(&mut self, num: usize);

    /// Creates a `Tracker`.
    fn create_tracker(self) -> Self::Tracker;
}

impl Progress for () {
    type Tracker = ();

    fn add_assets(&mut self, _: usize) {}

    fn create_tracker(self) -> () {
        ()
    }
}

/// A progress tracker which is passed to the `Loader`
/// in order to check how many assets are loaded.
#[derive(Default)]
pub struct ProgressCounter {
    errors: Arc<Mutex<Vec<Error>>>,
    num_assets: usize,
    num_failed: Arc<AtomicUsize>,
    num_loading: Arc<AtomicUsize>,
}

impl ProgressCounter {
    /// Creates a new `Progress` struct.
    pub fn new() -> Self {
        Default::default()
    }

    /// Removes all errors and returns them.
    pub fn errors(&self) -> Vec<Error> {
        let mut lock = self.errors.lock();
        let rv = lock.drain(..).collect();

        rv
    }

    /// Returns the number of assets this struct is tracking.
    pub fn num_assets(&self) -> usize {
        self.num_assets
    }

    /// Returns the number of assets that have failed.
    pub fn num_failed(&self) -> usize {
        self.num_failed.load(Ordering::Relaxed)
    }

    /// Returns the number of assets that are still loading.
    pub fn num_loading(&self) -> usize {
        self.num_loading.load(Ordering::Relaxed)
    }

    /// Returns the number of assets this struct is tracking.
    pub fn num_finished(&self) -> usize {
        self.num_assets - self.num_loading()
    }

    /// Returns `Completion::Complete` if all tracked assets are finished.
    pub fn complete(&self) -> Completion {
        match (
            self.num_failed.load(Ordering::Relaxed),
            self.num_loading.load(Ordering::Relaxed),
        ) {
            (0, 0) => Completion::Complete,
            (0, _) => Completion::Loading,
            (_, _) => Completion::Failed,
        }
    }

    /// Returns `true` if all assets have been imported without error.
    pub fn is_complete(&self) -> bool {
        self.complete() == Completion::Complete
    }
}

impl<'a> Progress for &'a mut ProgressCounter {
    type Tracker = ProgressCounterTracker;

    fn add_assets(&mut self, num: usize) {
        self.num_assets += num;
    }

    fn create_tracker(self) -> Self::Tracker {
        let errors = self.errors.clone();
        let num_failed = self.num_failed.clone();
        let num_loading = self.num_loading.clone();
        num_loading.fetch_add(1, Ordering::Relaxed);

        ProgressCounterTracker {
            errors,
            num_failed,
            num_loading,
        }
    }
}

/// Progress tracker for `ProgressCounter`.
#[derive(Default)]
pub struct ProgressCounterTracker {
    errors: Arc<Mutex<Vec<Error>>>,
    num_failed: Arc<AtomicUsize>,
    num_loading: Arc<AtomicUsize>,
}

impl Tracker for ProgressCounterTracker {
    fn success(self: Box<Self>) {
        self.num_loading.fetch_sub(1, Ordering::Relaxed);
    }

    fn fail(self: Box<Self>, e: Error) {
        self.errors.lock().push(e);
        self.num_failed.fetch_add(1, Ordering::Relaxed);
    }
}

/// The `Tracker` trait which will be used by the loader to report
/// back to `Progress`.
pub trait Tracker: Send + 'static {
    // TODO: maybe add handles as parameters?
    /// Called if the asset could be imported.
    fn success(self: Box<Self>);
    /// Called if the asset couldn't be imported to an error.
    fn fail(self: Box<Self>, e: Error);
}

impl Tracker for () {
    fn success(self: Box<Self>) {}
    fn fail(self: Box<Self>, e: Error) {
        eprintln!("error: {}", e);
        e.iter().skip(1).for_each(|e| eprintln!("caused by: {}", e));
        eprintln!("note: to handle the error, use a `Progress` other than `()`");
    }
}
