extern crate getopts;
extern crate serde_json;
extern crate udt;

extern crate punchtunnel;

use std::env;
use std::fs::File;
use std::iter::FromIterator;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket};
use std::process::exit;
use std::thread;
use std::time::Duration;

use getopts::Options;

use udt::{SocketFamily, SocketType, UdtOpts, UdtSocket};

use punchtunnel::key::Key;
use punchtunnel::relay_client::{RelayClient, ClientConfiguration};
use punchtunnel::relay_protocol::ConnectRequest;

#[derive(Clone)]
enum ConnectionSource {
    StdIo
}

fn serve(connection_source: ConnectionSource, conf: ClientConfiguration) -> Result<(), String> {
    udt::init();
    
    let server_socket = UdtSocket::new(SocketFamily::AFInet, SocketType::Stream)
        .map_err(|e| format!("Could not create UDT socket: {}", e.err_msg))?;
    server_socket
        .setsockopt(UdtOpts::UDT_RENDEZVOUS, true)
        .map_err(|e| {
            format!("Could not set UDT socket to rendezvous mode: {}", e.err_msg)
        })?;
    let local_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
    server_socket.bind(local_addr).map_err(|e| {
        format!(
            "Could not bind UDT socket to '{}': {}",
            local_addr,
            e.err_msg
        )
    })?;
    
    let local_port = server_socket.getsockname().map_err(|e| format!("Could not determine the local port of the UDT socket: {}", e.err_msg))?.port();
    
    let relay_client = RelayClient::new(&conf.relay_hostname)?;
    
    match relay_client.connect(ConnectRequest {
        connect_key: conf.connect_key,
        port: local_port,
    })?.server {
        Some((remote_addr, secret)) => {
            eprintln!("Connecting to {}", remote_addr);
            server_socket.connect(remote_addr).map_err(|e| {
                format!(
                    "Could not connect UDT socket to remote address '{}': {}",
                    remote_addr,
                    e.err_msg
                )
            })?;
            eprintln!("Sending");
            server_socket.send(format!("HEHEI").as_bytes()).map_err(|e| format!("Could not send data: {}", e.err_msg))?;
            server_socket.close();
            eprintln!("Done");
            Ok(())
        },
        None => {
            Err(format!("No server address received from the relay server"))
        }
    }
}

fn usage(program: &str, opts: Options) -> String {
    let brief = format!(
        "Usage: {} [OPTION]... CONF_FILENAME stdio",
        program
    );
    format!("{}", opts.usage(&brief))
}

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

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    let matches = match opts.parse(&args[1..]) {
        Ok(x) => x,
        Err(e) => {
            eprintln!("{}", usage(&program, opts));
            return Err(format!("Could not parse command line: {}", e));
        }
    };

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

    if matches.free.len() != 2 {
        eprintln!("{}", usage(&program, opts));
        return Err("Could not parse command line: wrong number of arguments".to_string());
    }

    let conf_filename = matches.free[0].clone();
    let conf_file = File::open(&conf_filename).map_err(|x| {
        format!(
            "Could not open configuration file '{}': {}",
            conf_filename,
            x
        )
    })?;
    let conf = serde_json::from_reader(conf_file).map_err(|x| {
        format!(
            "Could not read configuration file '{}': {}",
            conf_filename,
            x
        )
    })?;

    match matches.free[1].as_ref() {
        "stdio" => {
            serve(ConnectionSource::StdIo, conf)?;
        }
        _ => {
            eprintln!("{}", usage(&program, opts));
            return Err(format!(
                "Could not parse command line: unknown connection source type '{}'",
                matches.free[1]
            ));
        }
    }
    Ok(())
}

fn main() {
    match run() {
        Ok(()) => {}
        Err(x) => {
            eprintln!("Fatal error: {}", x);
            exit(1);
        }
    }
}