Documentation
use std::fmt;
use std::io::{Read, Write};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use super::error::Result;
use super::error::Error::ResponseError;

#[derive(Debug, Clone)]
pub enum OpCode {
    Request = 1,
    Response = 2
}

impl OpCode {
    pub fn from_i32(i: i32) -> Option<OpCode> {
        match i {
            1 => Some(OpCode::Request),
            2 => Some(OpCode::Response),
            _ => None
        }
    }
}

impl fmt::Display for OpCode {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            OpCode::Request => write!(fmt, "OP_REQUEST"),
            OpCode::Response => write!(fmt, "OP_RESPONSE")
        }
    }
}

#[derive(Debug, Clone)]
pub struct Header {
    pub message_length: i32,
    pub request_id: i32,
    pub response_to: i32,
    pub op_code: OpCode
}

impl Header {
    pub fn new(message_length: i32, request_id: i32, response_to: i32, op_code: OpCode) -> Header {
        Header {
            message_length: message_length,
            request_id: request_id,
            response_to: response_to,
            op_code: op_code
        }
    }

    pub fn new_request(message_length: i32, request_id: i32) -> Header {
        Header::new(message_length, request_id, 0, OpCode::Request)
    }

    pub fn new_response(message_length: i32, response_to: i32) -> Header {
        Header::new(message_length, 0, response_to, OpCode::Response)
    }

    pub fn write<W: Write>(&self, buffer: &mut W) -> Result<()> {
        buffer.write_i32::<LittleEndian>(self.message_length)?;
        buffer.write_i32::<LittleEndian>(self.request_id)?;
        buffer.write_i32::<LittleEndian>(self.response_to)?;
        buffer.write_i32::<LittleEndian>(self.op_code.clone() as i32)?;

        Ok(())
    }

    pub fn read<R: Read>(buffer: &mut R) -> Result<Header> {
        let message_length = buffer.read_i32::<LittleEndian>()?;
        let request_id = buffer.read_i32::<LittleEndian>()?;
        let response_to = buffer.read_i32::<LittleEndian>()?;
        let op_code_i32 = buffer.read_i32::<LittleEndian>()?;

        let op_code = match OpCode::from_i32(op_code_i32) {
            Some(code) => code,
            _ => return Err(ResponseError(format!("Invalid header opcode from server: {}.", op_code_i32)))
        };

        Ok(Header::new(message_length, request_id, response_to, op_code))
    }
}