use std::convert::{From, TryFrom};

use rosc::{OscMessage, OscType};
use thiserror::Error;

#[derive(Debug, Clone, Error)]
pub enum MessageError {
    #[error("Invalid address `{0}`")]
    InvalidAddr(String),
    #[error("Invalid arguments")]
    InvalidArgs,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Request {
    InitZero(i32, i32),
    X(i32, i32),
    Y(i32, i32),
    Z(i32, i32),
    H(i32, i32),
    S(i32, i32),
    Sdg(i32, i32),
    T(i32, i32),
    Tdg(i32, i32),
    CX(i32, i32, i32, i32),
    Mz(i32, i32),
}

impl TryFrom<OscMessage> for Request {
    type Error = anyhow::Error;

    fn try_from(msg: OscMessage) -> anyhow::Result<Request> {
        let OscMessage { addr, args } = msg;
        let args = args.into_iter()
                       .map(|x| x.int().ok_or(MessageError::InvalidArgs))
                       .collect::<Result<Vec<_>, _>>()?;
        let get = |n: usize| args.get(n).map(|x| *x).ok_or(MessageError::InvalidArgs);
        match addr.as_str() {
            "/InitZero" => Ok(Request::InitZero(get(0)?, get(1)?)),
            "/X" => Ok(Request::X(get(0)?, get(1)?)),
            "/Y" => Ok(Request::Y(get(0)?, get(1)?)),
            "/Z" => Ok(Request::Z(get(0)?, get(1)?)),
            "/H" => Ok(Request::H(get(0)?, get(1)?)),
            "/S" => Ok(Request::S(get(0)?, get(1)?)),
            "/Sdg" => Ok(Request::Sdg(get(0)?, get(1)?)),
            "/T" => Ok(Request::T(get(0)?, get(1)?)),
            "/Tdg" => Ok(Request::Tdg(get(0)?, get(1)?)),
            "/CX" => Ok(Request::CX(get(0)?, get(1)?, get(2)?, get(3)?)),
            "/Mz" => Ok(Request::Mz(get(0)?, get(1)?)),
            _ => Err(MessageError::InvalidAddr(addr).into())
        }
    }
}

impl From<&Request> for OscMessage {
    fn from(msg: &Request) -> OscMessage {
        match msg {
            Request::InitZero(n1, n2) => OscMessage { addr: "/InitZero".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::X(n1, n2) => OscMessage { addr: "/X".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::Y(n1, n2) => OscMessage { addr: "/Y".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::Z(n1, n2) => OscMessage { addr: "/Z".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::H(n1, n2) => OscMessage { addr: "/H".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::S(n1, n2) => OscMessage { addr: "/S".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::Sdg(n1, n2) => OscMessage { addr: "/Sdg".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::T(n1, n2) => OscMessage { addr: "/T".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::Tdg(n1, n2) => OscMessage { addr: "/Tdg".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
            Request::CX(n1, n2, n3, n4) => OscMessage { addr: "/CX".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2), OscType::Int(*n3), OscType::Int(*n4)] },
            Request::Mz(n1, n2) => OscMessage { addr: "/Mz".to_owned(), args: vec![OscType::Int(*n1), OscType::Int(*n2)] },
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Response {
    Mz(i32, f32),
}

impl TryFrom<OscMessage> for Response {
    type Error = anyhow::Error;

    fn try_from(msg: OscMessage) -> anyhow::Result<Response> {
        let OscMessage { addr, args } = msg;
        match addr.as_str() {
            "/Mz" => Ok(Response::Mz(args.get(0).and_then(|x| x.clone().int()).ok_or(MessageError::InvalidArgs)?,
                                     args.get(1).and_then(|x| x.clone().float()).ok_or(MessageError::InvalidArgs)?)),
            _ => Err(MessageError::InvalidAddr(addr).into())
        }
    }
}

impl From<&Response> for OscMessage {
    fn from(msg: &Response) -> OscMessage {
        match msg {
            Response::Mz(n1, f1) => OscMessage { addr: "/Mz".to_owned(), args: vec![OscType::Int(*n1), OscType::Float(*f1)] },
        }
    }
}
