pub mod add;
pub mod blocker;
pub mod calibration;
pub mod cborsink;
pub mod cosinus;
pub mod cumulation;
pub mod derivation;
pub mod directsource;
pub mod divide;
pub mod fifo;
pub mod first_value;
pub mod fixedvalues;
pub mod greater_than;
pub mod invert;
pub mod jsonlsink;
pub mod jsonlsource;
pub mod last_calm_point;
pub mod linear_regression;
pub mod maximum;
pub mod mean;
pub mod minimum;
pub mod multiplex;
pub mod multiply;
pub mod not;
pub mod pipeline;
pub mod single_not_null;
pub mod sinus;
pub mod storage;
pub mod subtract;
pub mod verificationsink;

use crate::{
    error, CalibrationSource, GetCalibration, Item, Proceed, Result, Scope,
};
use indexmap::IndexSet;
use snafu::{ensure, OptionExt, ResultExt};
use std::io::Write;
use std::rc::Weak;
use std::sync::RwLock;

/// A trait for a bin which provides output data.
pub trait SourceBin {
    fn get_source_data(&self, source: &SourceId) -> Result<Item>;
}

impl<S: SourceBin + ?Sized> SourceBin for Box<S> {
    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
        (**self).get_source_data(source)
    }
}

pub trait SinkBin {}

impl<S: SinkBin + ?Sized> SinkBin for Box<S> {}

pub trait SourceOnlyBin: SourceBin + std::fmt::Debug {
    fn fetch_next(&mut self, iteration: &Iteration) -> Result<Proceed>;
}

pub trait SourceSinkBin: SinkBin + SourceBin + Calculator {}
impl<T: SinkBin + SourceBin + Calculator> SourceSinkBin for T {}

pub trait SinkOnlyBin: SinkBin + Calculator {}

#[derive(Debug)]
pub struct SourceOnlyBinProcessor {
    bin: Box<SourceOnlyBin>,
    previous_iteration: Option<Iteration>,
    proceed: Proceed,
}

impl SourceOnlyBinProcessor {
    pub fn new(bin: Box<SourceOnlyBin>) -> Self {
        SourceOnlyBinProcessor {
            bin,
            previous_iteration: None,
            proceed: Proceed::Continue,
        }
    }
}

pub struct SourceSinkBinProcessor {
    bin: Box<SourceSinkBin>,
    previous_iteration: Option<Iteration>,
}

impl SinkBin for SourceSinkBinProcessor {}

impl Calculator for SourceSinkBinProcessor {
    fn calculate(&mut self, iteration: &Iteration) -> Result<()> {
        if self.previous_iteration != Some(iteration.clone()) {
            self.bin.calculate(iteration)?;
        }
        self.previous_iteration = Some(iteration.clone());
        Ok(())
    }
}

impl SourceBin for SourceSinkBinProcessor {
    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
        self.bin.get_source_data(source)
    }
}

pub struct SinkOnlyBinProcessor {
    bin: Box<SinkOnlyBin>,
    previous_iteration: Option<Iteration>,
}

impl Calculator for SinkOnlyBinProcessor {
    fn calculate(&mut self, iteration: &Iteration) -> Result<()> {
        if self.previous_iteration != Some(iteration.clone()) {
            self.bin.calculate(iteration)?;
        }
        self.previous_iteration = Some(iteration.clone());
        Ok(())
    }
}

impl SinkOnlyBinProcessor {
    pub fn new(bin: Box<SinkOnlyBin>) -> Self {
        SinkOnlyBinProcessor {
            bin,
            previous_iteration: None,
        }
    }
}

impl SourceBin for SourceOnlyBinProcessor {
    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
        self.bin.get_source_data(source)
    }
}

impl SourceSinkBinProcessor {
    pub fn new(bin: Box<SourceSinkBin>) -> Self {
        SourceSinkBinProcessor {
            bin,
            previous_iteration: None,
        }
    }
}

impl SourceOnlyBin for SourceOnlyBinProcessor {
    fn fetch_next(&mut self, iteration: &Iteration) -> Result<Proceed> {
        if self.proceed == Proceed::Stop {
            Ok(Proceed::Stop)
        } else {
            self.previous_iteration = match self.previous_iteration {
                None => {
                    self.proceed = self.bin.fetch_next(iteration)?;
                    Some(iteration.clone())
                }
                Some(ref it) if it == iteration => {
                    self.previous_iteration.clone()
                }
                Some(_) => {
                    self.proceed = self.bin.fetch_next(iteration)?;
                    Some(iteration.clone())
                }
            };
            Ok(self.proceed.clone())
        }
    }
}

pub trait Calculator {
    fn calculate(&mut self, iteration: &Iteration) -> Result<()>;

    fn initialize(&mut self) -> Result<()> {
        Ok(())
    }
}

impl<T: Calculator + ?Sized> Calculator for Box<T> {
    fn calculate(&mut self, iteration: &Iteration) -> Result<()> {
        (**self).calculate(iteration)
    }

    fn initialize(&mut self) -> Result<()> {
        (**self).initialize()
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum SourceOnlyDescription {
    JsonLSource(jsonlsource::Description),
    DirectSource(directsource::Description),
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum SourceSinkDescription {
    Add(add::Description),
    Blocker(blocker::Description),
    Calibration(calibration::Description),
    Cosinus(cosinus::Description),
    Cumulation(cumulation::Description),
    Derivation(derivation::Description),
    Divide(divide::Description),
    Fifo(fifo::Description),
    FirstValue(first_value::Description),
    FixedValues(fixedvalues::Description),
    GreaterThan(greater_than::Description),
    Invert(invert::Description),
    LastCalmPoint(last_calm_point::Description),
    LinearRegression(linear_regression::Description),
    Maximum(maximum::Description),
    Mean(mean::Description),
    Minimum(minimum::Description),
    Multiplex(multiplex::Description),
    Multiply(multiply::Description),
    Not(not::Description),
    Pipeline(pipeline::Description),
    SingleNotNull(single_not_null::Description),
    Sinus(sinus::Description),
    Subtract(subtract::Description),
    Storage(storage::Description),
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum SinkOnlyDescription {
    JsonLSink(jsonlsink::Description),
    CborSink(cborsink::Description),
    VerificationSink(verificationsink::Description),
}

impl BinDescription for SinkOnlyDescription {
    type Bin = Box<SinkOnlyBin>;

    fn check_validity(
        &self,
        scope: &Scope,
        getcal: &mut GetCalibration,
    ) -> Result<()> {
        use self::SinkOnlyDescription::*;
        match *self {
            JsonLSink(ref d) => d.check_validity(scope, getcal),
            CborSink(ref d) => d.check_validity(scope, getcal),
            VerificationSink(ref d) => d.check_validity(scope, getcal),
        }
    }

    fn bin_type(&self) -> &'static str {
        use self::SinkOnlyDescription::*;
        match *self {
            JsonLSink(ref d) => d.bin_type(),
            CborSink(ref d) => d.bin_type(),
            VerificationSink(ref d) => d.bin_type(),
        }
    }
}

impl SinkNames for SinkOnlyDescription {
    fn sink_names(&self) -> IndexSet<String> {
        use self::SinkOnlyDescription::*;
        match *self {
            JsonLSink(ref d) => d.sink_names(),
            CborSink(ref d) => d.sink_names(),
            VerificationSink(ref d) => d.sink_names(),
        }
    }
}

impl SinkOnlyBinDescription for SinkOnlyDescription {
    fn build_bin(
        &self,
        scope: &Scope,
        env: &mut BinBuildEnvironment,
    ) -> Result<Self::Bin> {
        use self::SinkOnlyDescription::*;
        let b: Box<SinkOnlyBin> = match *self {
            JsonLSink(ref d) => Box::new(d.build_bin(scope, env)?),
            CborSink(ref d) => Box::new(d.build_bin(scope, env)?),
            VerificationSink(ref d) => Box::new(d.build_bin(scope, env)?),
        };
        Ok(b)
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", untagged)]
pub enum Description {
    SourceOnly(SourceOnlyDescription),
    SourceSink(SourceSinkDescription),
    SinkOnly(SinkOnlyDescription),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceReference {
    pub bin: Option<String>,
    pub source: String,
}

impl BinDescription for SourceSinkDescription {
    type Bin = Box<SourceSinkBin>;

    fn check_validity(
        &self,
        scope: &Scope,
        getcal: &mut GetCalibration,
    ) -> Result<()> {
        use self::SourceSinkDescription::*;
        match *self {
            Add(ref d) => d.check_validity(scope, getcal),
            Blocker(ref d) => d.check_validity(scope, getcal),
            Calibration(ref d) => d.check_validity(scope, getcal),
            Cosinus(ref d) => d.check_validity(scope, getcal),
            Cumulation(ref d) => d.check_validity(scope, getcal),
            Derivation(ref d) => d.check_validity(scope, getcal),
            Divide(ref d) => d.check_validity(scope, getcal),
            Fifo(ref d) => d.check_validity(scope, getcal),
            FirstValue(ref d) => d.check_validity(scope, getcal),
            FixedValues(ref d) => d.check_validity(scope, getcal),
            GreaterThan(ref d) => d.check_validity(scope, getcal),
            Invert(ref d) => d.check_validity(scope, getcal),
            LastCalmPoint(ref d) => d.check_validity(scope, getcal),
            LinearRegression(ref d) => d.check_validity(scope, getcal),
            Maximum(ref d) => d.check_validity(scope, getcal),
            Mean(ref d) => d.check_validity(scope, getcal),
            Minimum(ref d) => d.check_validity(scope, getcal),
            Multiplex(ref d) => d.check_validity(scope, getcal),
            Multiply(ref d) => d.check_validity(scope, getcal),
            Not(ref d) => d.check_validity(scope, getcal),
            Pipeline(ref d) => d.check_validity(scope, getcal),
            SingleNotNull(ref d) => d.check_validity(scope, getcal),
            Sinus(ref d) => d.check_validity(scope, getcal),
            Subtract(ref d) => d.check_validity(scope, getcal),
            Storage(ref d) => d.check_validity(scope, getcal),
        }
    }

    fn bin_type(&self) -> &'static str {
        use self::SourceSinkDescription::*;
        match *self {
            Add(ref d) => d.bin_type(),
            Blocker(ref d) => d.bin_type(),
            Calibration(ref d) => d.bin_type(),
            Cosinus(ref d) => d.bin_type(),
            Cumulation(ref d) => d.bin_type(),
            Derivation(ref d) => d.bin_type(),
            Divide(ref d) => d.bin_type(),
            Fifo(ref d) => d.bin_type(),
            FirstValue(ref d) => d.bin_type(),
            FixedValues(ref d) => d.bin_type(),
            GreaterThan(ref d) => d.bin_type(),
            Invert(ref d) => d.bin_type(),
            LastCalmPoint(ref d) => d.bin_type(),
            LinearRegression(ref d) => d.bin_type(),
            Maximum(ref d) => d.bin_type(),
            Mean(ref d) => d.bin_type(),
            Minimum(ref d) => d.bin_type(),
            Multiplex(ref d) => d.bin_type(),
            Multiply(ref d) => d.bin_type(),
            Not(ref d) => d.bin_type(),
            Pipeline(ref d) => d.bin_type(),
            SingleNotNull(ref d) => d.bin_type(),
            Sinus(ref d) => d.bin_type(),
            Subtract(ref d) => d.bin_type(),
            Storage(ref d) => d.bin_type(),
        }
    }
}

impl WriteDot for SourceSinkDescription {
    fn write_dot<W: Write>(&self, w: &mut W, name: &str) -> Result<()> {
        use self::SourceSinkDescription::*;
        match *self {
            Add(ref d) => d.write_dot(w, name),
            Blocker(ref d) => d.write_dot(w, name),
            Calibration(ref d) => d.write_dot(w, name),
            Cosinus(ref d) => d.write_dot(w, name),
            Cumulation(ref d) => d.write_dot(w, name),
            Derivation(ref d) => d.write_dot(w, name),
            Divide(ref d) => d.write_dot(w, name),
            Fifo(ref d) => d.write_dot(w, name),
            FirstValue(ref d) => d.write_dot(w, name),
            FixedValues(ref d) => d.write_dot(w, name),
            GreaterThan(ref d) => d.write_dot(w, name),
            Invert(ref d) => d.write_dot(w, name),
            LastCalmPoint(ref d) => d.write_dot(w, name),
            LinearRegression(ref d) => d.write_dot(w, name),
            Maximum(ref d) => d.write_dot(w, name),
            Mean(ref d) => d.write_dot(w, name),
            Minimum(ref d) => d.write_dot(w, name),
            Multiplex(ref d) => d.write_dot(w, name),
            Multiply(ref d) => d.write_dot(w, name),
            Not(ref d) => d.write_dot(w, name),
            Pipeline(ref d) => d.write_dot(w, name),
            SingleNotNull(ref d) => d.write_dot(w, name),
            Sinus(ref d) => d.write_dot(w, name),
            Subtract(ref d) => d.write_dot(w, name),
            Storage(ref d) => d.write_dot(w, name),
        }
    }
}

impl SourceSinkBinDescription for SourceSinkDescription {
    fn build_bin(
        &self,
        s: &Scope,
        e: &mut BinBuildEnvironment,
    ) -> Result<Self::Bin> {
        use self::SourceSinkDescription::*;
        Ok(match *self {
            Add(ref d) => Box::new(d.build_bin(s, e)?),
            Blocker(ref d) => Box::new(d.build_bin(s, e)?),
            Calibration(ref d) => Box::new(d.build_bin(s, e)?),
            Cosinus(ref d) => Box::new(d.build_bin(s, e)?),
            Cumulation(ref d) => Box::new(d.build_bin(s, e)?),
            Derivation(ref d) => Box::new(d.build_bin(s, e)?),
            Divide(ref d) => Box::new(d.build_bin(s, e)?),
            Fifo(ref d) => Box::new(d.build_bin(s, e)?),
            FirstValue(ref d) => Box::new(d.build_bin(s, e)?),
            FixedValues(ref d) => Box::new(d.build_bin(s, e)?),
            GreaterThan(ref d) => Box::new(d.build_bin(s, e)?),
            Invert(ref d) => Box::new(d.build_bin(s, e)?),
            LastCalmPoint(ref d) => Box::new(d.build_bin(s, e)?),
            LinearRegression(ref d) => Box::new(d.build_bin(s, e)?),
            Maximum(ref d) => Box::new(d.build_bin(s, e)?),
            Mean(ref d) => Box::new(d.build_bin(s, e)?),
            Minimum(ref d) => Box::new(d.build_bin(s, e)?),
            Multiplex(ref d) => Box::new(d.build_bin(s, e)?),
            Multiply(ref d) => Box::new(d.build_bin(s, e)?),
            Not(ref d) => Box::new(d.build_bin(s, e)?),
            Pipeline(ref d) => Box::new(d.build_bin(s, e)?),
            SingleNotNull(ref d) => Box::new(d.build_bin(s, e)?),
            Sinus(ref d) => Box::new(d.build_bin(s, e)?),
            Subtract(ref d) => Box::new(d.build_bin(s, e)?),
            Storage(ref d) => Box::new(d.build_bin(s, e)?),
        })
    }
}

impl SinkNames for SourceSinkDescription {
    fn sink_names(&self) -> IndexSet<String> {
        use self::SourceSinkDescription::*;
        match *self {
            Add(ref d) => d.sink_names(),
            Blocker(ref d) => d.sink_names(),
            Calibration(ref d) => d.sink_names(),
            Cosinus(ref d) => d.sink_names(),
            Cumulation(ref d) => d.sink_names(),
            Derivation(ref d) => d.sink_names(),
            Divide(ref d) => d.sink_names(),
            Fifo(ref d) => d.sink_names(),
            FirstValue(ref d) => d.sink_names(),
            FixedValues(ref d) => d.sink_names(),
            GreaterThan(ref d) => d.sink_names(),
            Invert(ref d) => d.sink_names(),
            LastCalmPoint(ref d) => d.sink_names(),
            LinearRegression(ref d) => d.sink_names(),
            Maximum(ref d) => d.sink_names(),
            Mean(ref d) => d.sink_names(),
            Minimum(ref d) => d.sink_names(),
            Multiplex(ref d) => d.sink_names(),
            Multiply(ref d) => d.sink_names(),
            Not(ref d) => d.sink_names(),
            Pipeline(ref d) => d.sink_names(),
            SingleNotNull(ref d) => d.sink_names(),
            Sinus(ref d) => d.sink_names(),
            Subtract(ref d) => d.sink_names(),
            Storage(ref d) => d.sink_names(),
        }
    }
}

impl SourceNames for SourceSinkDescription {
    fn source_names(&self) -> Result<IndexSet<String>> {
        use self::SourceSinkDescription::*;
        match *self {
            Add(ref d) => d.source_names(),
            Blocker(ref d) => d.source_names(),
            Calibration(ref d) => d.source_names(),
            Cosinus(ref d) => d.source_names(),
            Cumulation(ref d) => d.source_names(),
            Derivation(ref d) => d.source_names(),
            Divide(ref d) => d.source_names(),
            Fifo(ref d) => d.source_names(),
            FirstValue(ref d) => d.source_names(),
            FixedValues(ref d) => d.source_names(),
            GreaterThan(ref d) => d.source_names(),
            Invert(ref d) => d.source_names(),
            LastCalmPoint(ref d) => d.source_names(),
            LinearRegression(ref d) => d.source_names(),
            Maximum(ref d) => d.source_names(),
            Mean(ref d) => d.source_names(),
            Minimum(ref d) => d.source_names(),
            Multiplex(ref d) => d.source_names(),
            Multiply(ref d) => d.source_names(),
            Not(ref d) => d.source_names(),
            Pipeline(ref d) => d.source_names(),
            SingleNotNull(ref d) => d.source_names(),
            Sinus(ref d) => d.source_names(),
            Subtract(ref d) => d.source_names(),
            Storage(ref d) => d.source_names(),
        }
    }
}

pub trait SourceOnlyBinDescription: SourceNames + BinDescription {
    fn build_bin(&self, scope: &Scope) -> Result<Self::Bin>;
}

impl BinDescription for SourceOnlyDescription {
    type Bin = Box<SourceOnlyBin>;

    fn check_validity(
        &self,
        scope: &Scope,
        getcal: &mut GetCalibration,
    ) -> Result<()> {
        use self::SourceOnlyDescription::*;
        match *self {
            JsonLSource(ref d) => d.check_validity(scope, getcal),
            DirectSource(ref d) => d.check_validity(scope, getcal),
        }
    }

    fn bin_type(&self) -> &'static str {
        use self::SourceOnlyDescription::*;
        match *self {
            JsonLSource(ref d) => d.bin_type(),
            DirectSource(ref d) => d.bin_type(),
        }
    }
}

impl SourceNames for SourceOnlyDescription {
    fn source_names(&self) -> Result<IndexSet<String>> {
        use self::SourceOnlyDescription::*;
        match *self {
            JsonLSource(ref d) => d.source_names(),
            DirectSource(ref d) => d.source_names(),
        }
    }
}

impl SourceOnlyBinDescription for SourceOnlyDescription {
    fn build_bin(&self, scope: &Scope) -> Result<Self::Bin> {
        use self::SourceOnlyDescription::*;
        let b: Box<SourceOnlyBin> = match *self {
            JsonLSource(ref d) => Box::new(d.build_bin(scope)?),
            DirectSource(ref d) => Box::new(d.build_bin(scope)?),
        };
        Ok(b)
    }
}

pub trait SourceSinkBinDescription:
    SourceNames + SinkNames + BinDescription
{
    fn build_bin(
        &self,
        scope: &Scope,
        env: &mut BinBuildEnvironment,
    ) -> Result<Self::Bin>;
}

pub trait SinkOnlyBinDescription: SinkNames + BinDescription {
    fn build_bin(
        &self,
        scope: &Scope,
        env: &mut BinBuildEnvironment,
    ) -> Result<Self::Bin>;
}

pub trait SinkNames {
    fn sink_names(&self) -> IndexSet<String>;

    /// Check that all incoming sinks will be fed by a source.
    /// The sink_names parameter mast match *exactly* the set
    /// of sinks which will be found in the bin.
    fn check_sink_names(
        &self,
        scope: &Scope,
        sink_names: &IndexSet<String>,
    ) -> Result<()> {
        ensure!(
            sink_names == &self.sink_names(),
            error::InvalidSinkNames {
                scope: scope.clone(),
                expected: self.sink_names().clone(),
                received: sink_names.clone(),
            }
        );
        Ok(())
    }
}

pub trait SinkBinDescription: BinDescription + SinkNames {}

impl<T: BinDescription + SinkNames> SinkBinDescription for T {}

pub trait SourceBinDescription: BinDescription + SourceNames {}

impl<T: BinDescription + SourceNames> SourceBinDescription for T {}

pub trait SourceNames {
    fn source_names(&self) -> Result<IndexSet<String>>;
}

pub trait BinDescription {
    type Bin;

    fn check_validity(
        &self,
        scope: &Scope,
        get_calibration: &mut GetCalibration,
    ) -> Result<()>;

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

pub trait WriteDot {
    fn write_dot<W: Write>(&self, w: &mut W, name: &str) -> Result<()>;
}

pub trait WriteDotSimple {}

impl<T: SourceNames + SinkNames + BinDescription + WriteDotSimple> WriteDot
    for T
{
    fn write_dot<W: Write>(&self, w: &mut W, name: &str) -> Result<()> {
        write_dot_bin(
            w,
            name,
            self.bin_type(),
            &self.sink_names(),
            &self.source_names()?,
        )
    }
}

#[derive(Debug, PartialEq, Clone, Default)]
pub struct Iteration {
    pub index: usize,
}

impl Iteration {
    pub fn iterate(&mut self) {
        self.index = self.index.checked_add(1usize).unwrap();
    }
}

#[derive(Debug, PartialEq)]
pub struct SourceId {
    pub id: String,
}

impl SourceId {
    pub fn new(id: &str) -> Self {
        SourceId { id: id.to_string() }
    }
}

pub trait FetchItem {
    fn fetch_item(&self, scope: &Scope) -> Result<Item>;
}

pub struct DataSource<T: SourceBin> {
    pub data_provider: Weak<RwLock<T>>,
    pub source: SourceId,
}

impl<T: SourceBin> FetchItem for DataSource<T> {
    fn fetch_item(&self, scope: &Scope) -> Result<Item> {
        let provider_mut = self.data_provider.upgrade().context(
            error::DataProviderGone {
                scope: scope.clone(),
            },
        )?;
        let provider = provider_mut.write().unwrap();
        provider.get_source_data(&self.source)
    }
}

trait CheckContains {
    fn check_contains(
        &self,
        scope: &Scope,
        bin_type: &str,
        value: &str,
    ) -> Result<()>;
}

impl CheckContains for IndexSet<String> {
    fn check_contains(
        &self,
        scope: &Scope,
        bin_type: &str,
        value: &str,
    ) -> Result<()> {
        ensure!(
            self.contains(&value.to_string()),
            error::InvalidSourceName {
                bin_type: bin_type.to_string(),
                name: format!("{:?}", value),
                scope: scope.clone(),
            }
        );
        Ok(())
    }
}

fn write_dot_bin<W: Write>(
    w: &mut W,
    name: &str,
    bin_type: &str,
    sinks: &IndexSet<String>,
    sources: &IndexSet<String>,
) -> Result<()> {
    writeln!(w, "{} [label=<", name).context(error::Io)?;
    write_dot_bin_table(w, name, bin_type, sinks, sources)?;
    writeln!(w, ">];").context(error::Io)?;
    Ok(())
}

fn write_dot_bin_table<W: Write>(
    w: &mut W,
    name: &str,
    bin_type: &str,
    sinks: &IndexSet<String>,
    sources: &IndexSet<String>,
) -> Result<()> {
    let cols = match (sinks.is_empty(), sources.is_empty()) {
        (false, false) => 2,
        (true, false) | (false, true) => 1,
        (true, true) => 0,
    };

    writeln!(w, "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">")
        .context(error::Io)?;

    writeln!(w, "<tr>").context(error::Io)?;
    writeln!(w, "<td colspan=\"{}\">", cols).context(error::Io)?;
    writeln!(
        w,
        "<font point-size=\"12\">{}</font><br/>{}",
        name, bin_type
    )
    .context(error::Io)?;
    writeln!(w, "</td>").context(error::Io)?;
    writeln!(w, "</tr>").context(error::Io)?;

    if cols > 0 {
        writeln!(w, "<tr>").context(error::Io)?;
        if !sinks.is_empty() {
            writeln!(w, "<td>").context(error::Io)?;
            write_dot_socket_table(w, sinks, "_sink")?;
            writeln!(w, "</td>").context(error::Io)?;
        }
        if !sources.is_empty() {
            writeln!(w, "<td>").context(error::Io)?;
            write_dot_socket_table(w, sources, "_source")?;
            writeln!(w, "</td>").context(error::Io)?;
        }
        writeln!(w, "</tr>").context(error::Io)?;
    }

    writeln!(w, "</table>").context(error::Io)?;

    Ok(())
}

fn write_dot_socket_table<W: Write>(
    w: &mut W,
    items: &IndexSet<String>,
    suffix: &str,
) -> Result<()> {
    writeln!(w, "<table border=\"0\" cellborder=\"0\" cellspacing=\"0\">")
        .context(error::Io)?;
    for item in items {
        writeln!(w, "<tr>").context(error::Io)?;
        writeln!(w, "<td port=\"{}{}\">{}</td>", item, suffix, item)
            .context(error::Io)?;
        writeln!(w, "</tr>").context(error::Io)?;
    }

    writeln!(w, "</table>").context(error::Io)?;

    Ok(())
}

pub trait BinBuildEnvironment: GetCalibration {
    fn resolve(&mut self, id: &str) -> Result<Box<FetchItem>>;
}

static SINK_A: &'static str = "a";
static SINK_B: &'static str = "b";
static SINK_INPUT: &'static str = "input";
static SINK_SELECT: &'static str = "select";
static SINK_TRIGGER: &'static str = "trigger";
static SINK_X: &'static str = "x";
static SINK_Y: &'static str = "y";

static SOURCE_OUTPUT: &'static str = "output";

fn build_names<S: ToString>(names: &[S]) -> IndexSet<String> {
    names.iter().map(S::to_string).collect()
}

fn sink_names_a_b() -> IndexSet<String> {
    build_names(&[SINK_A, SINK_B])
}

fn sink_names_input_trigger() -> IndexSet<String> {
    build_names(&[SINK_INPUT, SINK_TRIGGER])
}

fn sink_names_trigger() -> IndexSet<String> {
    build_names(&[SINK_TRIGGER])
}

fn sink_names_input_select() -> IndexSet<String> {
    build_names(&[SINK_INPUT, SINK_SELECT])
}

fn sink_names_input() -> IndexSet<String> {
    build_names(&[SINK_INPUT])
}

fn source_names_output() -> IndexSet<String> {
    build_names(&[SOURCE_OUTPUT])
}
