#![allow(dead_code)]

use std::collections::HashSet;
use std::io::prelude::*;
use std::io::BufReader;
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::path::PathBuf;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;

pub struct Server {
    messages: Option<Sender<Message>>,
    addr: Addr,
    thread: Option<thread::JoinHandle<()>>,
}

enum Message {
    Read(String),
    Write(String),
}

enum Addr {
    Tcp(SocketAddr),
    Unix(PathBuf),
}

fn run(stream: impl Read + Write, rx: &Receiver<Message>) {
    let mut socket = BufReader::new(stream);
    for msg in rx.iter() {
        match msg {
            Message::Read(ref expected) => {
                let mut expected = &expected[..];
                let mut expected_headers = HashSet::new();
                while let Some(i) = expected.find('\n') {
                    let line = &expected[..i + 1];
                    expected = &expected[i + 1..];
                    expected_headers.insert(line);
                    if line == "\r\n" {
                        break;
                    }
                }

                let mut expected_len = None;
                while !expected_headers.is_empty() {
                    let mut actual = String::new();
                    t!(socket.read_line(&mut actual));
                    if actual.starts_with("Content-Length") {
                        let len = actual.split(": ").nth(1).unwrap();
                        expected_len = len.trim().parse().ok();
                    }
                    // various versions of libcurl do different things here
                    if actual == "Proxy-Connection: Keep-Alive\r\n" {
                        continue;
                    }
                    if expected_headers.remove(&actual[..]) {
                        continue;
                    }

                    let mut found = None;
                    for header in expected_headers.iter() {
                        if lines_match(header, &actual) {
                            found = Some(*header);
                            break;
                        }
                    }
                    if let Some(found) = found {
                        expected_headers.remove(&found);
                        continue;
                    }
                    panic!(
                        "unexpected header: {:?} (remaining headers {:?})",
                        actual, expected_headers
                    );
                }
                for header in expected_headers {
                    panic!("expected header but not found: {:?}", header);
                }

                let mut line = String::new();
                let mut socket = match expected_len {
                    Some(amt) => socket.by_ref().take(amt),
                    None => socket.by_ref().take(expected.len() as u64),
                };
                while socket.limit() > 0 {
                    line.truncate(0);
                    t!(socket.read_line(&mut line));
                    if line.is_empty() {
                        break;
                    }
                    if expected.is_empty() {
                        panic!("unexpected line: {:?}", line);
                    }
                    let i = expected.find('\n').unwrap_or(expected.len() - 1);
                    let expected_line = &expected[..i + 1];
                    expected = &expected[i + 1..];
                    if lines_match(expected_line, &line) {
                        continue;
                    }
                    panic!(
                        "lines didn't match:\n\
                         expected: {:?}\n\
                         actual:   {:?}\n",
                        expected_line, line
                    )
                }
                if !expected.is_empty() {
                    println!("didn't get expected data: {:?}", expected);
                }
            }
            Message::Write(ref to_write) => {
                t!(socket.get_mut().write_all(to_write.as_bytes()));
                return;
            }
        }
    }

    let mut dst = Vec::new();
    t!(socket.read_to_end(&mut dst));
    assert_eq!(dst.len(), 0);
}

fn lines_match(expected: &str, mut actual: &str) -> bool {
    for (i, part) in expected.split("[..]").enumerate() {
        match actual.find(part) {
            Some(j) => {
                if i == 0 && j != 0 {
                    return false;
                }
                actual = &actual[j + part.len()..];
            }
            None => return false,
        }
    }
    actual.is_empty() || expected.ends_with("[..]")
}

impl Server {
    pub fn new() -> Server {
        let listener = t!(TcpListener::bind("127.0.0.1:0"));
        let addr = t!(listener.local_addr());
        let (tx, rx) = channel();
        let thread = thread::spawn(move || run(listener.accept().unwrap().0, &rx));
        Server {
            messages: Some(tx),
            addr: Addr::Tcp(addr),
            thread: Some(thread),
        }
    }

    #[cfg(not(windows))]
    pub fn new_unix() -> Server {
        use std::os::unix::net::UnixListener;

        let path = "/tmp/easy_server.sock";
        std::fs::remove_file(path).ok();
        let listener = t!(UnixListener::bind(path));
        let (tx, rx) = channel();
        let thread = thread::spawn(move || run(listener.incoming().next().unwrap().unwrap(), &rx));
        Server {
            messages: Some(tx),
            addr: Addr::Unix(path.into()),
            thread: Some(thread),
        }
    }

    pub fn receive(&self, msg: &str) {
        self.msg(Message::Read(self.replace_port(msg)));
    }

    fn replace_port(&self, msg: &str) -> String {
        match &self.addr {
            Addr::Tcp(addr) => msg.replace("$PORT", &addr.port().to_string()),
            Addr::Unix(_) => msg.to_string(),
        }
    }

    pub fn send(&self, msg: &str) {
        self.msg(Message::Write(self.replace_port(msg)));
    }

    fn msg(&self, msg: Message) {
        t!(self.messages.as_ref().unwrap().send(msg));
    }

    pub fn addr(&self) -> &SocketAddr {
        match &self.addr {
            Addr::Tcp(addr) => addr,
            Addr::Unix(_) => panic!("server is a UnixListener"),
        }
    }

    #[cfg(not(windows))]
    pub fn path(&self) -> &str {
        match &self.addr {
            Addr::Tcp(_) => panic!("server is a TcpListener"),
            Addr::Unix(p) => p.as_os_str().to_str().unwrap(),
        }
    }

    pub fn url(&self, path: &str) -> String {
        match &self.addr {
            Addr::Tcp(addr) => format!("http://{}{}", addr, path),
            Addr::Unix(_) => format!("http://localhost{}", path),
        }
    }
}

impl Drop for Server {
    fn drop(&mut self) {
        match &self.addr {
            Addr::Tcp(addr) => drop(TcpStream::connect(addr)),
            Addr::Unix(p) => t!(std::fs::remove_file(p)),
        }

        drop(self.messages.take());
        let res = self.thread.take().unwrap().join();
        if !thread::panicking() {
            t!(res);
        } else if let Err(e) = res {
            println!("child server thread also failed: {:?}", e);
        }
    }
}
