extern crate serde;
extern crate serde_json;

use snafu::{ensure, OptionExt};

use super::{
    BinDescription, GetCalibration, Item, Iteration, Proceed, Result,
    Scope, SourceBin, SourceId, SourceNames, SourceOnlyBin,
    SourceOnlyBinDescription,
};
use crate::error;
use indexmap::IndexSet;
use std::collections::VecDeque;

static BIN_TYPE: &'static str = "directsource";

#[derive(Debug)]
pub struct Bin {
    scope: Scope,
    columns: IndexSet<String>,
    rows: VecDeque<Vec<Item>>,

    outputs: Vec<Item>,
}

impl SourceOnlyBin for Bin {
    fn fetch_next(&mut self, _iteration: &Iteration) -> Result<Proceed> {
        if let Some(row) = self.rows.pop_front() {
            self.outputs = row;
            Ok(Proceed::Continue)
        } else {
            Ok(Proceed::Stop)
        }
    }
}

impl SourceBin for Bin {
    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
        let index = self.columns.iter().position(|s| s == &source.id);

        let index = index.context(error::InvalidSourceName {
            scope: self.scope.clone(),
            name: source.id.to_string(),
            bin_type: BIN_TYPE.to_string(),
        })?;
        Ok(self.outputs.get(index).cloned().unwrap())
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Description {
    pub columns: IndexSet<String>,
    pub rows: VecDeque<Vec<Item>>,
}

impl BinDescription for Description {
    type Bin = Bin;

    fn check_validity(
        &self,
        _scope: &Scope,
        _get_calibration: &mut GetCalibration,
    ) -> Result<()> {
        ensure!(!self.columns.is_empty(), error::InvalidDirectSourceData);
        ensure!(
            self.columns.iter().collect::<IndexSet<_>>().len()
                == self.columns.len(),
            error::InvalidDirectSourceData
        );

        for row in &self.rows {
            ensure!(
                row.len() == self.columns.len(),
                error::InvalidDirectSourceData
            );
        }
        Ok(())
    }

    fn bin_type(&self) -> &'static str {
        BIN_TYPE
    }
}

impl SourceNames for Description {
    fn source_names(&self) -> Result<IndexSet<String>> {
        Ok(self.columns.clone())
    }
}

impl SourceOnlyBinDescription for Description {
    fn build_bin(&self, scope: &Scope) -> Result<Self::Bin> {
        Ok(Bin {
            scope: scope.clone(),
            columns: self.columns.clone(),
            rows: self.rows.clone(),
            outputs: self.columns.iter().map(|_| Item::Nothing).collect(),
        })
    }
}
