// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use option::{Option, Some, None};
use result::{Ok, Err};
use io::net::ip::SocketAddr;
use io::{Reader, Writer};
use io::{io_error, EndOfFile};
use rt::rtio::{RtioSocket, RtioUdpSocket, IoFactory, LocalIo};

pub struct UdpSocket {
    priv obj: ~RtioUdpSocket
}

impl UdpSocket {
    pub fn bind(addr: SocketAddr) -> Option<UdpSocket> {
        let mut io = LocalIo::borrow();
        match io.get().udp_bind(addr) {
            Ok(s) => Some(UdpSocket { obj: s }),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
                None
            }
        }
    }

    pub fn recvfrom(&mut self, buf: &mut [u8]) -> Option<(uint, SocketAddr)> {
        match self.obj.recvfrom(buf) {
            Ok((nread, src)) => Some((nread, src)),
            Err(ioerr) => {
                // EOF is indicated by returning None
                if ioerr.kind != EndOfFile {
                    io_error::cond.raise(ioerr);
                }
                None
            }
        }
    }

    pub fn sendto(&mut self, buf: &[u8], dst: SocketAddr) {
        match self.obj.sendto(buf, dst) {
            Ok(_) => (),
            Err(ioerr) => io_error::cond.raise(ioerr),
        }
    }

    pub fn connect(self, other: SocketAddr) -> UdpStream {
        UdpStream { socket: self, connectedTo: other }
    }

    pub fn socket_name(&mut self) -> Option<SocketAddr> {
        match self.obj.socket_name() {
            Ok(sn) => Some(sn),
            Err(ioerr) => {
                debug!("failed to get socket name: {:?}", ioerr);
                io_error::cond.raise(ioerr);
                None
            }
        }
    }
}

pub struct UdpStream {
    priv socket: UdpSocket,
    priv connectedTo: SocketAddr
}

impl UdpStream {
    pub fn as_socket<T>(&mut self, f: |&mut UdpSocket| -> T) -> T {
        f(&mut self.socket)
    }

    pub fn disconnect(self) -> UdpSocket { self.socket }
}

impl Reader for UdpStream {
    fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
        let peer = self.connectedTo;
        self.as_socket(|sock| {
            match sock.recvfrom(buf) {
                Some((_nread, src)) if src != peer => Some(0),
                Some((nread, _src)) => Some(nread),
                None => None,
            }
        })
    }

    fn eof(&mut self) -> bool { fail!() }
}

impl Writer for UdpStream {
    fn write(&mut self, buf: &[u8]) {
        self.as_socket(|sock| sock.sendto(buf, self.connectedTo));
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use rt::test::*;
    use io::net::ip::{Ipv4Addr, SocketAddr};
    use io::*;
    use option::{Some, None};
    use rt::comm::oneshot;

    #[test]  #[ignore]
    fn bind_error() {
        do run_in_mt_newsched_task {
            let mut called = false;
            io_error::cond.trap(|e| {
                assert!(e.kind == PermissionDenied);
                called = true;
            }).inside(|| {
                let addr = SocketAddr { ip: Ipv4Addr(0, 0, 0, 0), port: 1 };
                let socket = UdpSocket::bind(addr);
                assert!(socket.is_none());
            });
            assert!(called);
        }
    }

    #[test]
    fn socket_smoke_test_ip4() {
        do run_in_mt_newsched_task {
            let server_ip = next_test_ip4();
            let client_ip = next_test_ip4();
            let (port, chan) = oneshot();

            do spawntask {
                match UdpSocket::bind(server_ip) {
                    Some(ref mut server) => {
                        chan.send(());
                        let mut buf = [0];
                        match server.recvfrom(buf) {
                            Some((nread, src)) => {
                                assert_eq!(nread, 1);
                                assert_eq!(buf[0], 99);
                                assert_eq!(src, client_ip);
                            }
                            None => fail!()
                        }
                    }
                    None => fail!()
                }
            }

            do spawntask {
                match UdpSocket::bind(client_ip) {
                    Some(ref mut client) => {
                        port.recv();
                        client.sendto([99], server_ip)
                    }
                    None => fail!()
                }
            }
        }
    }

    #[test]
    fn socket_smoke_test_ip6() {
        do run_in_mt_newsched_task {
            let server_ip = next_test_ip6();
            let client_ip = next_test_ip6();
            let (port, chan) = oneshot();

            do spawntask {
                match UdpSocket::bind(server_ip) {
                    Some(ref mut server) => {
                        chan.send(());
                        let mut buf = [0];
                        match server.recvfrom(buf) {
                            Some((nread, src)) => {
                                assert_eq!(nread, 1);
                                assert_eq!(buf[0], 99);
                                assert_eq!(src, client_ip);
                            }
                            None => fail!()
                        }
                    }
                    None => fail!()
                }
            }

            do spawntask {
                match UdpSocket::bind(client_ip) {
                    Some(ref mut client) => {
                        port.recv();
                        client.sendto([99], server_ip)
                    }
                    None => fail!()
                }
            }
        }
    }

    #[test]
    fn stream_smoke_test_ip4() {
        do run_in_mt_newsched_task {
            let server_ip = next_test_ip4();
            let client_ip = next_test_ip4();
            let (port, chan) = oneshot();

            do spawntask {
                match UdpSocket::bind(server_ip) {
                    Some(server) => {
                        let server = ~server;
                        let mut stream = server.connect(client_ip);
                        chan.send(());
                        let mut buf = [0];
                        match stream.read(buf) {
                            Some(nread) => {
                                assert_eq!(nread, 1);
                                assert_eq!(buf[0], 99);
                            }
                            None => fail!()
                        }
                    }
                    None => fail!()
                }
            }

            do spawntask {
                match UdpSocket::bind(client_ip) {
                    Some(client) => {
                        let client = ~client;
                        let mut stream = client.connect(server_ip);
                        port.recv();
                        stream.write([99]);
                    }
                    None => fail!()
                }
            }
        }
    }

    #[test]
    fn stream_smoke_test_ip6() {
        do run_in_mt_newsched_task {
            let server_ip = next_test_ip6();
            let client_ip = next_test_ip6();
            let (port, chan) = oneshot();

            do spawntask {
                match UdpSocket::bind(server_ip) {
                    Some(server) => {
                        let server = ~server;
                        let mut stream = server.connect(client_ip);
                        chan.send(());
                        let mut buf = [0];
                        match stream.read(buf) {
                            Some(nread) => {
                                assert_eq!(nread, 1);
                                assert_eq!(buf[0], 99);
                            }
                            None => fail!()
                        }
                    }
                    None => fail!()
                }
            }

            do spawntask {
                match UdpSocket::bind(client_ip) {
                    Some(client) => {
                        let client = ~client;
                        let mut stream = client.connect(server_ip);
                        port.recv();
                        stream.write([99]);
                    }
                    None => fail!()
                }
            }
        }
    }

    #[cfg(test)]
    fn socket_name(addr: SocketAddr) {
        do run_in_mt_newsched_task {
            do spawntask {
                let server = UdpSocket::bind(addr);

                assert!(server.is_some());
                let mut server = server.unwrap();

                // Make sure socket_name gives
                // us the socket we binded to.
                let so_name = server.socket_name();
                assert!(so_name.is_some());
                assert_eq!(addr, so_name.unwrap());

            }
        }
    }

    #[test]
    fn socket_name_ip4() {
        socket_name(next_test_ip4());
    }

    #[test]
    fn socket_name_ip6() {
        socket_name(next_test_ip6());
    }
}
