extern crate hyper;

mod error;
mod helper;
mod matching;

use hyper::Server;
use hyper::server::{Handler,Request,Response};
use hyper::net::Fresh;

use std::env::{args,current_dir,set_current_dir};
use std::fs::{read_dir};
use std::iter::Iterator;
use std::path::Path;

use error::{AloysError,aloys_error};
use matching::do_match;
use helper::{path_for,domain_for,with_file};

#[derive(Debug, Clone)]
enum Delegate {
    Forward(HTTPForward),
    Defer(Routes),
}

#[derive(Debug, Clone)]
struct HTTPForward {
    domn: String,
    port: u32,
}

#[derive(Debug, Clone)]
struct RouteSpec {
    conf_at:     String,
    for_path:    Option<String>,
    for_domain:  Option<String>,
    delegate_to: Delegate,
}

#[derive(Debug, Clone)]
struct Routes {
    routes: Vec<RouteSpec>,
}

impl Routes {
    /// Take a path and load the route specifications contained there.
    fn load_paths(dir: &Path) -> Result<Routes,AloysError> {
        let mut routes = vec![];
        for entry in try!(read_dir(dir)) {
            let entry_path = try!(entry).path();
            routes.push(try!(RouteSpec::from_path(entry_path.as_path())));
        }
        Ok(Routes { routes: routes })
    }

    /// Given a request route it appropriately, or raise an error.
    fn dispatch(&self, req: &Request, res: Response<Fresh>) {
        match self.find_delegate(&req) {
            Some(delegate) => delegate.run(req, res),
            None => {
                let _ = res.send(b"[no matching handler]");
            },
        }
    }

    fn find_delegate(&self, req: &Request) -> Option<&Delegate> {
        for d in self.routes.iter() {
            let mut matches = true;
            if let Some(ref d) = d.for_domain {
                if let Some(domain) = domain_for(req) {
                    matches = matches && do_match(d, domain);
                }
            }
            if let Some(ref p) = d.for_path {
                if let Some(req_path) = path_for(req) {
                    matches = matches && do_match(p, &req_path);
                } else {
                    matches = false;
                }
            }
            if matches {
                println!("using {:?}", d.conf_at);
                return Some(&d.delegate_to);
            }
        }
        return None;
    }
}

impl Handler for Routes {
    fn handle(&self, req: Request, res: Response<Fresh>) {
        self.dispatch(&req, res);
    }
}

impl RouteSpec {
    fn from_path(dir: &Path) -> Result<RouteSpec,AloysError> {
        println!("at dir {:?}", dir);
        let path   = with_file(&dir.join("path"),
                               None,
                               |s| Some(s));
        let domain = with_file(&dir.join("domain"),
                               None,
                               |s| Some(s));
        let mode   = with_file(&dir.join("mode"),
                               "http".to_string(),
                               |s| s);
        let delegate =
            if mode == "http" {
                let domn = with_file(&dir.join("host"),
                                     "localhost".to_string(),
                                     |s| s);
                let port = with_file(&dir.join("port"),
                                     Ok(80),
                                     |s| s.parse::<u32>() );
                let port_num = match port {
                    Ok(n)  => n,
                    Err(e) => return aloys_error(format!("Bad port number in `{:?}: {:?}`", dir, e)),
                };
                Delegate::Forward(HTTPForward { domn: domn,
                                                port: port_num })
            } else if mode == "aloys" {
                let fwd = with_file(&dir.join("conf"),
                                    "/dev/null".to_string(),
                                    |s| s);
                let rts = try!(Routes::load_paths(Path::new(&fwd)));
                Delegate::Defer(rts)
            } else {
                return aloys_error(format!("Unknown mode `{:?}` in `{:?}", mode, dir))
            };
        Ok(RouteSpec {
            conf_at: dir.to_string_lossy().to_string(),
            for_path: path,
            for_domain: domain,
            delegate_to: delegate
        })
    }
}

impl Delegate {
    fn run(&self, req: &Request, res: Response<Fresh>) {
        match self {
            &Delegate::Forward(ref fwd) => {
                let _ = res.send(format!("[forward to {:?}:{:?}]",
                                         fwd.domn,
                                         fwd.port,
                                         ).as_bytes());
            },
            &Delegate::Defer(ref rts) => {
                rts.dispatch(req, res);
            },
        }
    }
}


fn run_server() -> Result<(), AloysError> {
    if let Some(target_dir) = args().skip(1).next() {
        println!("switching to directory {:}", target_dir);
        try!(set_current_dir(Path::new(&target_dir)));
    }
    let cwd = try!(current_dir());
    let paths = try!(Routes::load_paths(cwd.as_path()));
    println!("Read routes: {:?}", paths);
    let srv = try!(Server::http("127.0.0.1:8080"));
    let _ = srv.handle(paths);
    Ok(())
}

fn main() {
    match run_server() {
        Ok(_) => (),
        Err(e) => println!("Got error: {:?}", e),
    };
}
