use std::error::Error;

use crate::account::account::Account;
use crate::account::node_ref::NodeRef;
use crate::account::state_node::StateNode;
use crate::serialization::state::Account as ProtoAccount;
use crate::serialization::state::NodeRef as ProtoNodeRef;
use crate::serialization::state::StateNode as ProtoStateNode;
use crate::serialization::state::{DBState as ProtoDBState, DBState_oneof_state};
use crate::traits::{Decode, Encode, Exception, Proto};
use protobuf::Message as ProtoMessage;
use protobuf::RepeatedField;

#[derive(Clone, Debug, PartialEq)]
pub struct DBState {
    pub account: Option<Account>,
    pub node: Option<StateNode>,
    pub ref_count: u32,
}

impl DBState {
    pub fn new(account: Option<Account>, node: Option<StateNode>, ref_count: u32) -> DBState {
        DBState {
            account: account,
            node: node,
            ref_count: ref_count,
        }
    }
}

impl Decode for DBState {
    fn decode(buffer: &[u8]) -> Result<DBState, Box<Error>> {
        let mut proto_db_state = ProtoDBState::new();
        proto_db_state.merge_from_bytes(buffer)?;
        let mut db_state = DBState::new(None, None, 0);
        let state = match proto_db_state.state {
            Some(state_node) => state_node,
            None => return Err(Box::new(Exception::new("ProtoDBState has no state node"))),
        };

        match state {
            DBState_oneof_state::account(data_acc) => {
                db_state.account = Some(Account {
                    balance: data_acc.balance,
                    nonce: data_acc.nonce,
                });
            }
            DBState_oneof_state::node(data_node) => {
                let mut refs: Vec<NodeRef> = vec![];
                for proto_node_ref in data_node.nodeRefs.into_iter() {
                    let r = NodeRef::new(&proto_node_ref.address, &proto_node_ref.child);
                    refs.push(r);
                }
                db_state.node = Some(StateNode::new(refs));
            }
        }
        db_state.ref_count = proto_db_state.refCount;

        Ok(db_state)
    }
}

impl Proto for DBState {
    type ProtoType = ProtoDBState;
    fn to_proto(&self) -> Result<Self::ProtoType, Box<Error>> {
        let mut data = Self::ProtoType::new();
        match &self.account {
            Some(account) => {
                let mut proto_account = ProtoAccount::new();
                proto_account.set_balance(account.balance);
                proto_account.set_nonce(account.nonce);
                data.set_account(proto_account);
            }
            None => (),
        }

        match &self.node {
            Some(node) => {
                let mut proto_state_node = ProtoStateNode::new();
                let mut proto_refs: Vec<ProtoNodeRef> = Vec::with_capacity(node.node_refs.len());
                for tmp in &node.node_refs {
                    proto_refs.push(tmp.1.to_proto().unwrap());
                }
                let refs = RepeatedField::from(proto_refs);
                proto_state_node.set_nodeRefs(refs);
                data.set_node(proto_state_node);
            }
            None => (),
        }

        data.refCount = self.ref_count;

        Ok(data)
    }

    fn from_proto(_prototype: &Self::ProtoType) -> Result<Self, Box<Error>> {
        unimplemented!()
    }
}

impl Encode for DBState {
    fn encode(&self) -> Result<Vec<u8>, Box<Error>> {
        let proto_db_state = self.to_proto()?;
        Ok(proto_db_state.write_to_bytes()?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_makes_a_dbstate() {
        let account = Account::new(10, 20);
        let dbstate = DBState::new(Some(account.clone()), None, 0);
        assert_eq!(dbstate.account, Some(account.clone()));
    }

    #[test]
    fn it_encodes_like_javascript_for_non_zero() {
        let addr_slice = vec![109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let child = vec![
            137, 28, 167, 193, 135, 226, 96, 56, 197, 123, 221, 237, 249, 5, 134, 194, 38, 184,
            100, 131, 41, 152, 47, 186, 185, 70, 18, 162, 105, 115, 14, 42,
        ];
        let state_node = StateNode::new(vec![NodeRef::new(&addr_slice, &child)]);
        let dbstate: DBState = DBState::new(None, Some(state_node), 1);
        let encoding = dbstate.encode().unwrap();
        let javascript_encoding = vec![
            24, 1, 18, 58, 10, 56, 10, 20, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 18, 32, 137, 28, 167, 193, 135, 226, 96, 56, 197, 123, 221, 237, 249, 5, 134,
            194, 38, 184, 100, 131, 41, 152, 47, 186, 185, 70, 18, 162, 105, 115, 14, 42,
        ];
        let decoding = DBState::decode(&encoding).unwrap();
        assert_eq!(encoding, javascript_encoding);
        assert_eq!(decoding.account, dbstate.account);
        assert_eq!(decoding.node, dbstate.node);
        assert_eq!(decoding.ref_count, dbstate.ref_count);
    }

    #[test]
    fn it_encodes_like_javascript_for_zero() {
        let addr_slice = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let child = vec![
            0, 28, 0, 193, 0, 226, 0, 56, 0, 123, 0, 237, 0, 5, 0, 194, 0, 184, 0, 131, 0, 152, 0,
            186, 0, 70, 0, 162, 0, 115, 0, 42,
        ];
        let state_node = StateNode::new(vec![NodeRef::new(&addr_slice, &child)]);
        let dbstate: DBState = DBState::new(None, Some(state_node), 0);
        let encoding = dbstate.encode().unwrap();
        let javascript_encoding = vec![
            24, 0, 18, 58, 10, 56, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 18, 32, 0, 28, 0, 193, 0, 226, 0, 56, 0, 123, 0, 237, 0, 5, 0, 194, 0, 184, 0, 131,
            0, 152, 0, 186, 0, 70, 0, 162, 0, 115, 0, 42,
        ];
        let decoding = DBState::decode(&encoding).unwrap();
        assert_eq!(encoding, javascript_encoding);
        assert_eq!(decoding.account, dbstate.account);
        assert_eq!(decoding.node, dbstate.node);
        assert_eq!(decoding.ref_count, dbstate.ref_count);
    }
}
