use std::collections::{hash_map::DefaultHasher, VecDeque};
use std::env;
use std::hash::{Hash, Hasher};
use std::io;
use std::process::exit;
use std::sync::{Arc, RwLock};
use std::time::Instant;

use chainerror::prelude::v1::*;

pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

use varlink::{Connection, StringHashMap, StringHashSet, VarlinkService};

mod org_varlink_certification {
    #![doc = "This file was automatically generated by the varlink rust generator"]
    #![allow(non_camel_case_types)]
    #![allow(non_snake_case)]
    include!(concat!(env!("OUT_DIR"), "/org.varlink.certification.rs"));
}
use org_varlink_certification::*;

#[cfg(test)]
mod test; // Main

fn print_usage(program: &str, opts: &getopts::Options) {
    let brief = format!(
        "Usage: {} [--timeout <sec>] [--varlink=<address>] [--client]",
        program
    );
    print!("{}", opts.usage(&brief));
}

fn main() -> Result<()> {
    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();

    let mut opts = getopts::Options::new();
    opts.optopt("", "varlink", "varlink address URL", "<ADDRESS>");
    opts.optopt(
        "b",
        "bridge",
        "Command to execute and connect to",
        "<COMMAND>",
    );
    opts.optflag("", "client", "run in client mode");
    opts.optflag("h", "help", "print this help menu");
    opts.optopt("", "timeout", "server timeout", "<seconds>");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            print_usage(&program, &opts);
            eprintln!("{}", f);
            exit(1);
        }
    };

    if matches.opt_present("h") {
        print_usage(&program, &opts);
        return Ok(());
    }

    let client_mode = matches.opt_present("client");

    let timeout = matches
        .opt_str("timeout")
        .unwrap_or_default()
        .parse::<u64>()
        .unwrap_or(1000);

    if client_mode {
        let connection = match matches.opt_str("varlink") {
            None => match matches.opt_str("bridge") {
                Some(bridge) => Connection::with_bridge(&bridge)
                    .context(format!("Connection::with_bridge({})", bridge))?,
                None => Connection::with_activate(&format!(
                    "{} \
                     --varlink=$VARLINK_ADDRESS",
                    program
                ))?,
            },
            Some(address) => Connection::with_address(&address)
                .context(format!("Connection::with_address({})", address))?,
        };
        run_client(connection)?
    } else if let Some(address) = matches.opt_str("varlink") {
        run_server(&address, timeout)?
    } else {
        print_usage(&program, &opts);
        eprintln!("Need varlink address in server mode.");
        exit(1);
    }

    Ok(())
}

// Client

fn run_client(connection: Arc<RwLock<varlink::Connection>>) -> Result<()> {
    let mut iface = org_varlink_certification::VarlinkClient::new(connection);

    let ret = iface.start().call()?;
    eprintln!("{:#?}", ret);

    let client_id = ret.client_id;

    let ret = iface.test01(client_id.clone()).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test02(client_id.clone(), ret.bool).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test03(client_id.clone(), ret.int).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test04(client_id.clone(), ret.float).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test05(client_id.clone(), ret.string).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface
        .test06(client_id.clone(), ret.bool, ret.int, ret.float, ret.string)
        .call()?;
    eprintln!("{:#?}", ret);

    let ret = iface
        .test07(
            client_id.clone(),
            Test07_Args_struct {
                bool: ret.r#struct.bool,
                int: ret.r#struct.int,
                float: ret.r#struct.float,
                string: ret.r#struct.string,
            },
        )
        .call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test08(client_id.clone(), ret.map).call()?;
    eprintln!("{:#?}", ret);

    let ret = iface.test09(client_id.clone(), ret.set).call()?;
    eprintln!("{:#?}", ret);

    let mut ret_array = Vec::new();

    for ret in iface.test10(client_id.clone(), ret.mytype).more()? {
        let ret = ret?;
        eprintln!("{:#?}", ret);
        ret_array.push(ret.string.clone());
    }

    iface.test11(client_id.clone(), ret_array).oneway()?;

    let ret = iface.end(client_id).call()?;
    eprintln!("{:#?}", ret);

    Ok(())
}

// Server

fn new_mytype() -> io::Result<MyType> {
    let mut mytype_dictionary: StringHashMap<String> = StringHashMap::new();
    mytype_dictionary.insert("foo".into(), "Foo".into());
    mytype_dictionary.insert("bar".into(), "Bar".into());

    let mut mytype_stringset = varlink::StringHashSet::new();
    mytype_stringset.insert("one".into());
    mytype_stringset.insert("two".into());
    mytype_stringset.insert("three".into());

    let mut ele1: StringHashMap<Interface_foo> = StringHashMap::new();
    ele1.insert("foo".into(), Interface_foo::foo);
    ele1.insert("bar".into(), Interface_foo::bar);

    let mut ele2: StringHashMap<Interface_foo> = StringHashMap::new();
    ele2.insert("one".into(), Interface_foo::foo);
    ele2.insert("two".into(), Interface_foo::bar);

    Ok(MyType {
        object: serde_json::from_str(
            r#"{"method": "org.varlink.certification.Test09",
                       "parameters": {"map": {"foo": "Foo", "bar": "Bar"}}}"#,
        )?,
        r#enum: MyType_enum::two,
        r#struct: MyType_struct {
            first: 1,
            second: "2".into(),
        },
        array: vec!["one".into(), "two".into(), "three".into()],
        dictionary: mytype_dictionary,
        stringset: mytype_stringset,
        nullable: None,
        nullable_array_struct: None,
        interface: Interface {
            foo: Some(vec![None, Some(ele1), None, Some(ele2)]),
            anon: Interface_anon {
                foo: true,
                bar: false,
            },
        },
    })
}

macro_rules! check_call_expr {
    ($c:ident, $pat:expr, $wants:expr) => {{
        let check = $pat;
        if !check {
            let got: serde_json::Value =
                serde_json::to_value($c.get_request().unwrap()).map_err(varlink::map_context!())?;
            return $c.reply_certification_error(
                serde_json::to_value($wants).map_err(varlink::map_context!())?,
                got,
            );
        }
    }};
}

macro_rules! check_call_normal {
    ($c:ident, $test:expr, $got:ty, $wants:expr) => {{
        let wants = $wants;
        let check = match $c.get_request() {
            Some(&varlink::Request {
                more: Some(true), ..
            })
            | Some(&varlink::Request {
                oneway: Some(true), ..
            })
            | Some(&varlink::Request {
                upgrade: Some(true),
                ..
            }) => false,
            Some(&varlink::Request {
                method: ref m,
                parameters: Some(ref p),
                ..
            }) if m == $test => {
                let v: ::std::result::Result<$got, serde_json::Error> =
                    serde_json::from_value(p.clone());
                match v {
                    Ok(w) => wants == w,
                    _ => false,
                }
            }

            _ => false,
        };
        if !check {
            let got: serde_json::Value =
                serde_json::to_value($c.get_request().unwrap()).map_err(varlink::map_context!())?;
            let wants = serde_json::to_value(wants).map_err(varlink::map_context!())?;
            return $c.reply_certification_error(
                serde_json::to_value(varlink::Request {
                    more: None,
                    oneway: None,
                    upgrade: None,
                    method: $test.into(),
                    parameters: Some(wants),
                })
                .map_err(varlink::map_context!())?,
                got,
            );
        }
    }};
}

macro_rules! check_call_more {
    ($c:ident, $test:expr, $got:ty, $wants:expr) => {{
        let wants = $wants;
        let check = match $c.get_request() {
            Some(&varlink::Request {
                oneway: Some(true), ..
            })
            | Some(&varlink::Request {
                upgrade: Some(true),
                ..
            }) => false,
            Some(&varlink::Request {
                more: Some(true),
                method: ref m,
                parameters: Some(ref p),
                ..
            }) if m == $test => {
                let v: ::std::result::Result<$got, serde_json::Error> =
                    serde_json::from_value(p.clone());
                match v {
                    Ok(w) => wants == w,
                    _ => false,
                }
            }

            _ => false,
        };
        if !check {
            let got: serde_json::Value =
                serde_json::to_value($c.get_request().unwrap()).map_err(varlink::map_context!())?;
            let wants = serde_json::to_value(wants).map_err(varlink::map_context!())?;
            return $c.reply_certification_error(
                serde_json::to_value(varlink::Request {
                    more: None,
                    oneway: None,
                    upgrade: None,
                    method: $test.into(),
                    parameters: Some(wants),
                })
                .map_err(varlink::map_context!())?,
                got,
            );
        }
    }};
}

macro_rules! check_call_oneway {
    ($c:ident, $test:expr, $got:ty, $wants:expr) => {{
        let wants = $wants;
        let check = match $c.get_request() {
            Some(&varlink::Request {
                more: Some(true), ..
            })
            | Some(&varlink::Request {
                upgrade: Some(true),
                ..
            }) => false,
            Some(&varlink::Request {
                oneway: Some(true),
                method: ref m,
                parameters: Some(ref p),
                ..
            }) if m == $test => {
                let v: ::std::result::Result<$got, serde_json::Error> =
                    serde_json::from_value(p.clone());
                match v {
                    Ok(w) => wants == w,
                    _ => false,
                }
            }

            _ => false,
        };
        if !check {
            let got: serde_json::Value =
                serde_json::to_value($c.get_request().unwrap()).map_err(varlink::map_context!())?;
            let wants = serde_json::to_value(wants).map_err(varlink::map_context!())?;
            return $c.reply_certification_error(
                serde_json::to_value(varlink::Request {
                    more: None,
                    oneway: None,
                    upgrade: None,
                    method: $test.into(),
                    parameters: Some(wants),
                })
                .map_err(varlink::map_context!())?,
                got,
            );
        }
    }};
}

impl org_varlink_certification::VarlinkInterface for CertInterface {
    fn start(&self, call: &mut dyn Call_Start) -> varlink::Result<()> {
        check_call_expr!(
            call,
            match call.get_request() {
                Some(&varlink::Request {
                    more: Some(true), ..
                })
                | Some(&varlink::Request {
                    upgrade: Some(true),
                    ..
                })
                | Some(&varlink::Request {
                    oneway: Some(true), ..
                }) => false,
                Some(&varlink::Request {
                    method: ref m,
                    parameters: ref p,
                    ..
                }) if m == "org.varlink.certification.Start"
                    && (*p == None
                        || *p == Some(serde_json::Value::Object(serde_json::Map::new()))) =>
                {
                    true
                }

                _ => false,
            },
            varlink::Request {
                more: None,
                oneway: Some(false),
                upgrade: None,
                method: "org.varlink.certification.Start".into(),
                parameters: None,
            }
        );

        call.reply(self.new_client_id())
    }

    fn test01(&self, call: &mut dyn Call_Test01, client_id: String) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test01", "Test02") {
            return call.reply_client_id_error();
        }

        check_call_normal!(
            call,
            "org.varlink.certification.Test01",
            Test01_Args,
            Test01_Args { client_id }
        );

        call.reply(true)
    }

    fn test02(
        &self,
        call: &mut dyn Call_Test02,
        client_id: String,
        _bool_: bool,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test02", "Test03") {
            return call.reply_client_id_error();
        }

        check_call_normal!(
            call,
            "org.varlink.certification.Test02",
            Test02_Args,
            Test02_Args {
                client_id,
                bool: true,
            }
        );
        call.reply(1)
    }

    fn test03(
        &self,
        call: &mut dyn Call_Test03,
        client_id: String,
        _int: i64,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test03", "Test04") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.Test03",
            Test03_Args,
            Test03_Args { client_id, int: 1 }
        );

        call.reply(1.0)
    }

    fn test04(
        &self,
        call: &mut dyn Call_Test04,
        client_id: String,
        _float: f64,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test04", "Test05") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.Test04",
            Test04_Args,
            Test04_Args {
                client_id,
                float: 1.0,
            }
        );

        call.reply("ping".into())
    }

    fn test05(
        &self,
        call: &mut dyn Call_Test05,
        client_id: String,
        _string: String,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test05", "Test06") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.Test05",
            Test05_Args,
            Test05_Args {
                client_id,
                string: "ping".into(),
            }
        );

        call.reply(false, 2, std::f64::consts::PI, "a lot of string".into())
    }

    fn test06(
        &self,
        call: &mut dyn Call_Test06,
        client_id: String,
        _bool_: bool,
        _int: i64,
        _float: f64,
        _string: String,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test06", "Test07") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.Test06",
            Test06_Args,
            Test06_Args {
                client_id,
                bool: false,
                int: 2,
                float: std::f64::consts::PI,
                string: "a lot of string".into(),
            }
        );

        call.reply(Test06_Reply_struct {
            bool: false,
            int: 2,
            float: std::f64::consts::PI,
            string: "a lot of string".into(),
        })
    }

    fn test07(
        &self,
        call: &mut dyn Call_Test07,
        client_id: String,
        _struct_: Test07_Args_struct,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test07", "Test08") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.Test07",
            Test07_Args,
            Test07_Args {
                client_id,
                r#struct: Test07_Args_struct {
                    bool: false,
                    int: 2,
                    float: std::f64::consts::PI,
                    string: "a lot of string".into(),
                },
            }
        );

        let mut map: StringHashMap<String> = StringHashMap::new();
        map.insert("bar".into(), "Bar".into());
        map.insert("foo".into(), "Foo".into());
        call.reply(map)
    }

    fn test08(
        &self,
        call: &mut dyn Call_Test08,
        client_id: String,
        _map: ::std::collections::HashMap<String, String>,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test08", "Test09") {
            return call.reply_client_id_error();
        }
        let mut map: StringHashMap<String> = StringHashMap::new();
        map.insert("bar".into(), "Bar".into());
        map.insert("foo".into(), "Foo".into());

        check_call_normal!(
            call,
            "org.varlink.certification.Test08",
            Test08_Args,
            Test08_Args { client_id, map }
        );

        let mut set = StringHashSet::new();
        set.insert("one".into());
        set.insert("two".into());
        set.insert("three".into());
        call.reply(set)
    }

    fn test09(
        &self,
        call: &mut dyn Call_Test09,
        client_id: String,
        _set: varlink::StringHashSet,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test09", "Test10") {
            return call.reply_client_id_error();
        }
        let mut set = StringHashSet::new();
        set.insert("one".into());
        set.insert("two".into());
        set.insert("three".into());

        check_call_normal!(
            call,
            "org.varlink.certification.Test09",
            Test09_Args,
            Test09_Args { client_id, set }
        );

        call.reply(new_mytype().map_err(varlink::map_context!())?)
    }

    fn test10(
        &self,
        call: &mut dyn Call_Test10,
        client_id: String,
        _mytype: MyType,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test10", "Test11") {
            return call.reply_client_id_error();
        }
        check_call_more!(
            call,
            "org.varlink.certification.Test10",
            Test10_Args,
            Test10_Args {
                client_id,
                mytype: new_mytype().map_err(varlink::map_context!())?,
            }
        );

        call.set_continues(true);
        for i in 1..11 {
            if i == 10 {
                call.set_continues(false);
            }
            call.reply(format!("Reply number {}", i))?
        }
        Ok(())
    }

    fn test11(
        &self,
        call: &mut dyn Call_Test11,
        client_id: String,
        _last_more_replies: Vec<String>,
    ) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "Test11", "End") {
            return call.reply_client_id_error();
        }
        let mut last_more_replies: Vec<String> = Vec::new();

        for i in 0..10 {
            last_more_replies.push(format!("Reply number {}", i + 1));
        }

        check_call_oneway!(
            call,
            "org.varlink.certification.Test11",
            Test11_Args,
            Test11_Args {
                client_id,
                last_more_replies,
            }
        );

        Ok(())
    }

    fn end(&self, call: &mut dyn Call_End, client_id: String) -> varlink::Result<()> {
        if !self.check_client_id(&client_id, "End", "End") {
            return call.reply_client_id_error();
        }
        check_call_normal!(
            call,
            "org.varlink.certification.End",
            End_Args,
            End_Args { client_id }
        );

        call.reply(true)
    }
}

struct Context {
    test: String,
}

struct ClientIds {
    lifetimes: VecDeque<(Instant, String)>,
    contexts: StringHashMap<Context>,
    max_lifetime: u64,
}

impl ClientIds {
    fn check_client_id(&mut self, client_id: &str, test: &str, next_test: &str) -> bool {
        self.check_lifetime_timeout();

        match self.contexts.get_mut(client_id) {
            Some(context) => {
                if context.test != test {
                    false
                } else {
                    context.test = next_test.into();
                    true
                }
            }
            _ => false,
        }
    }

    fn check_lifetime_timeout(&mut self) {
        loop {
            let pop = match self.lifetimes.front() {
                None => false,

                Some(&(ref instant, ref client_id)) => {
                    if instant.elapsed().as_secs() > self.max_lifetime {
                        self.contexts.remove(client_id);
                        true
                    } else {
                        false
                    }
                }
            };

            if !pop {
                break;
            }
            self.lifetimes.pop_front();
        }
    }

    fn new_client_id(&mut self) -> String {
        let now = Instant::now();
        let mut hasher = DefaultHasher::new();
        format!("{:?}", now).hash(&mut hasher);
        let client_id = format!("{:x}", hasher.finish());
        self.contexts.insert(
            client_id.clone(),
            Context {
                test: "Test01".into(),
            },
        );
        self.lifetimes.push_back((now, client_id.clone()));
        client_id
    }
}

struct CertInterface {
    pub client_ids: Arc<RwLock<ClientIds>>,
}

impl CertInterface {
    fn check_client_id(&self, client_id: &str, test: &str, next_test: &str) -> bool {
        let mut client_ids = self.client_ids.write().unwrap();
        client_ids.check_client_id(client_id, test, next_test)
    }

    fn new_client_id(&self) -> String {
        let mut client_ids = self.client_ids.write().unwrap();
        client_ids.new_client_id()
    }
}

pub fn run_server(address: &str, timeout: u64) -> varlink::Result<()> {
    let certinterface = CertInterface {
        client_ids: Arc::new(RwLock::new(ClientIds {
            lifetimes: VecDeque::new(),
            contexts: StringHashMap::new(),
            max_lifetime: 60 * 60 * 12,
        })),
    };

    let myinterface = new(Box::new(certinterface));
    let service = VarlinkService::new(
        "org.varlink",
        "Varlink Certification Suite",
        "0.1",
        "http://varlink.org",
        vec![Box::new(myinterface)],
    );

    if let Err(e) = varlink::listen(
        service,
        &address,
        &varlink::ListenConfig {
            idle_timeout: timeout,
            ..Default::default()
        },
    ) {
        match e.kind() {
            ::varlink::ErrorKind::Timeout => {}
            _ => return Err(e),
        }
    }
    Ok(())
}
