use std::net::{TcpListener, TcpStream, SocketAddr, Ipv4Addr};
use std::thread;
use elseware::common::{send_packet, recv_packet, PacketNetworkError};
use rand::{Rng, RngCore};
use libpso::{PacketParseError, PSOPacket};
use libpso::patch::packet::*;
use libpso::crypto::{CipherError, PSOCipher, NullCipher};
use libpso::crypto::pc::PSOPCCipher;

const PATCH_PORT: u16 = 11000;


#[derive(Debug)]
enum PatchError {
    PacketNetworkError(PacketNetworkError)
}

impl From<PacketNetworkError> for PatchError {
    fn from(err: PacketNetworkError) -> PatchError {
        PatchError::PacketNetworkError(err)
    }
}

#[derive(Debug)]
pub enum PatchPacket {
    PatchWelcomeReply(PatchWelcomeReply),
    StartFilePatching(StartFilePatching),
}


struct Client {
    running: bool,
    key_in: u32,
    key_out: u32,
    socket: TcpStream,
    cipher_in: Box<PSOCipher>,
    cipher_out: Box<PSOCipher>,
}


impl Client {
    fn new(socket: TcpStream) -> Client {
        let mut rng = rand::thread_rng();
        let key_in: u32 = rng.gen();
        let key_out: u32 = rng.gen();

        Client {
            running: true,
            key_in: key_in,
            key_out: key_out,
            socket: socket,
            cipher_in: Box::new(NullCipher {}),
            cipher_out: Box::new(NullCipher {}),
            //cipher_in: Box::new(PSOPCCipher::new(key_in)),
            //cipher_out: Box::new(PSOPCCipher::new(key_out)),
        }
    }

    fn send(&mut self, pkt: &PSOPacket) {
        match send_packet(&mut self.socket, &mut *self.cipher_out, pkt) {
            Ok(_) => {
                println!("[patch] send ({:?}): {:?}", self.socket, pkt);
            },
            Err(err) => {
                println!("[patch] error sending packet to {:?}: {:?}", self.socket, err);
                self.running = false;
            }
        }
    }

    fn handle_packet(&mut self, patch_pkt: &PatchPacket) -> Result<(), PatchError> {
        println!("[patch] recv({:?}): {:?}", self.socket, patch_pkt);

        match patch_pkt {
            PatchPacket::PatchWelcomeReply(pkt) => {
            },
            PatchPacket::StartFilePatching(pkt) => {
            },
        }
        
        Ok(())
    }
    
    fn run(mut self) {
        let welcome_pkt = PatchWelcome::new(self.key_out, self.key_in);
        self.send(&welcome_pkt);
        self.cipher_in = Box::new(PSOPCCipher::new(self.key_in));
        self.cipher_out = Box::new(PSOPCCipher::new(self.key_out));
        
        while self.running {
            let res = recv_packet(&mut self.socket, &mut *self.cipher_in)
                .and_then(|pkt| {
                    PatchPacket::from_bytes(&pkt)
                        .map_err(|err| err.into())
                })
                .map(|pkt| {
                    self.handle_packet(&pkt)
                });

            println!("res! {:?}", res);
            match res {
                Ok(_) => {},
                Err(err) => {
                    println!("[patch] error handling packet with {:?}: {:?}", self.socket, err);
                    self.running = false;
                }
            }
            
                /*.or_else(|err| {
                    println!("[patch] error handling packet with {:?}: {:?}", self.socket, err);
                    self.running = false;
                })?;*/
        }
    }
}

impl PatchPacket {
    pub fn from_bytes(data: &Vec<u8>) -> Result<PatchPacket, PacketParseError> {
        match data[2] {
            0x02 => Ok(PatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)),
            0x04 => Ok(PatchPacket::StartFilePatching(StartFilePatching::from_bytes(data)?)),
            _ => Err(PacketParseError::WrongPacketForServerType)
        }
    }
}


fn handle_client(socket: TcpStream) {
    let client = Client::new(socket);
    client.run();
}

pub fn patch_server_loop() {
    println!("[patch] starting server");
    let listener = TcpListener::bind(&SocketAddr::from((Ipv4Addr::new(0,0,0,0), PATCH_PORT))).unwrap();
    
    
    loop {
        match listener.accept() {
            Ok((socket, addr)) => {
                println!("[patch] accepted connection: {}", addr);
                thread::spawn(move || {
                    handle_client(socket);
                });
            }
            Err(e) => {
                println!("[patch] accepted connection error {:?}", e);
            }
        }
    }


    
}