//! Crypto-bank market module.

#![feature(test, try_from)]

extern crate bincode;
extern crate crypto_currency as currency;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;
extern crate test;

pub mod errors {
    error_chain! {
        errors {
            InvalidOrderKind(k: i64) {
                description("invalid order kind")
                display("invalid order kind: {}", k)
            }
        }
    }
}

use std::convert::TryFrom;
use currency::Pair;

/// Market identifiers.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub enum Market {
    Poloniex,
    Bitfinex,
}

/// Order kind can be either `Ask` when we are selling
/// or a `Bid` when we are trying to buy something.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub enum OrderKind {
    /// Ask or a sell.
    Ask,
    /// Bid or a buy.
    Bid,
}

/// Currency order details.
///
/// It's format used in databases and streams,
/// in places where  currency pair is always known.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct Order {
    /// Order kind, which is an `Ask` or a `Bid`.
    pub kind: OrderKind,
    /// Price rate is a price of a unit.
    pub rate: f32,
    /// Order volume.
    pub volume: f32,
    /// Order total value.
    pub total: Option<f32>,
}

impl Order {
    /// Gets total order value from struct.
    /// It is calculated if `total` contains `None` value.
    pub fn get_total(&self) -> f32 {
        match self.total {
            Some(total) => total,
            None => self.rate * self.volume,
        }
    }

    /// Sets calculated total value.
    pub fn calculate_total(&mut self) -> &Self {
        self.total = Some(self.rate * self.volume);
        self
    }
}

/// Currency trade details.
///
/// It's format used in databases and streams,
/// in places where  currency pair is always known.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct Trade {
    /// Trade ID, unique only for a market.
    pub id: i64,
    /// Trade order.
    pub order: Order,
    /// Trade timestamp.
    pub timestamp: i64,
}

/// Market order book.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct OrderBook {
    /// Currency pair.
    pub pair: Option<Pair>,
    /// List of asks.
    pub asks: Vec<Order>,
    /// List of bids.
    pub bids: Vec<Order>,
}

impl OrderBook {
    // TODO: removing on `order.amount == 0`.
    /// Inserts order to order book.
    pub fn insert(&mut self, order: Order) {
        match order.kind {
            OrderKind::Ask => self.asks.push(order),
            OrderKind::Bid => self.bids.push(order),
        }
    }
}

/// Tries to convert from integer to `OrderKind`.
/// Ask is `0` and Bid is `1.
impl TryFrom<i64> for OrderKind {
    type Error = errors::Error;

    fn try_from(k: i64) -> Result<Self, Self::Error> {
        match k {
            0 => Ok(OrderKind::Ask),
            1 => Ok(OrderKind::Bid),
            _ => Err(errors::ErrorKind::InvalidOrderKind(k).into()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;
    use bincode::{deserialize, serialize, Infinite};

    fn create_order() -> Order {
        Order {
            kind: OrderKind::Bid,
            rate: 0.00002789,
            volume: 1788.27536750,
            total: None,
        }
    }

    fn create_trade() -> Trade {
        Trade {
            id: 14179278,
            order: create_order(),
            timestamp: 1509576585,
        }
    }

    #[bench]
    fn serialize_order(b: &mut Bencher) {
        b.iter(|| serialize(&create_order(), Infinite).unwrap());
    }

    #[bench]
    fn serialize_trade(b: &mut Bencher) {
        b.iter(|| serialize(&create_trade(), Infinite).unwrap());
    }

    #[bench]
    fn deserialize_order(b: &mut Bencher) {
        let body = serialize(&create_order(), Infinite).unwrap();
        b.iter(|| deserialize::<Order>(&body).unwrap());
    }

    #[bench]
    fn deserialize_trade(b: &mut Bencher) {
        let body = serialize(&create_trade(), Infinite).unwrap();
        b.iter(|| deserialize::<Trade>(&body).unwrap());
    }

    #[bench]
    fn serdeser_trade(b: &mut Bencher) {
        let t = &create_trade();
        b.iter(|| {
            let body = serialize(t, Infinite).unwrap();
            let _trade: Trade = deserialize(&body).unwrap();
        });
    }
}
