#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]

macro_rules! ready {
    ($e:expr $(,)?) => {
        match $e {
            std::task::Poll::Ready(t) => t,
            std::task::Poll::Pending => return std::task::Poll::Pending,
        }
    };
}

#[macro_export]
macro_rules! cfg_fs {
    ($($item:item)*) => { $($item)* }
}

#[macro_export]
macro_rules! cfg_io_std {
    ($($item:item)*) => { $($item)* }
}

use futures::future;

// Load source
#[allow(warnings)]
#[path = "../src/fs/file.rs"]
mod file;
use file::File;

#[allow(warnings)]
#[path = "../src/io/blocking.rs"]
mod blocking;

// Load mocked types
mod support {
    pub(crate) mod mock_file;
    pub(crate) mod mock_pool;
}
pub(crate) use support::mock_pool as pool;

// Place them where the source expects them
pub(crate) mod io {
    pub(crate) use tokio::io::*;

    pub(crate) use crate::blocking;

    pub(crate) mod sys {
        pub(crate) use crate::support::mock_pool::{run, Blocking};
    }
}
pub(crate) mod fs {
    pub(crate) mod sys {
        pub(crate) use crate::support::mock_file::File;
        pub(crate) use crate::support::mock_pool::{run, Blocking};
    }

    pub(crate) use crate::support::mock_pool::asyncify;
}
pub(crate) mod sync {
    pub(crate) use tokio::sync::Mutex;
}
use fs::sys;

use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};

use std::io::SeekFrom;

const HELLO: &[u8] = b"hello world...";
const FOO: &[u8] = b"foo bar baz...";

#[test]
fn open_read() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO);

    let mut file = File::from_std(file);

    let mut buf = [0; 1024];
    let mut t = task::spawn(file.read(&mut buf));

    assert_eq!(0, pool::len());
    assert_pending!(t.poll());

    assert_eq!(1, mock.remaining());
    assert_eq!(1, pool::len());

    pool::run_one();

    assert_eq!(0, mock.remaining());
    assert!(t.is_woken());

    let n = assert_ready_ok!(t.poll());
    assert_eq!(n, HELLO.len());
    assert_eq!(&buf[..n], HELLO);
}

#[test]
fn read_twice_before_dispatch() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO);

    let mut file = File::from_std(file);

    let mut buf = [0; 1024];
    let mut t = task::spawn(file.read(&mut buf));

    assert_pending!(t.poll());
    assert_pending!(t.poll());

    assert_eq!(pool::len(), 1);
    pool::run_one();

    assert!(t.is_woken());

    let n = assert_ready_ok!(t.poll());
    assert_eq!(&buf[..n], HELLO);
}

#[test]
fn read_with_smaller_buf() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO);

    let mut file = File::from_std(file);

    {
        let mut buf = [0; 32];
        let mut t = task::spawn(file.read(&mut buf));
        assert_pending!(t.poll());
    }

    pool::run_one();

    {
        let mut buf = [0; 4];
        let mut t = task::spawn(file.read(&mut buf));
        let n = assert_ready_ok!(t.poll());
        assert_eq!(n, 4);
        assert_eq!(&buf[..], &HELLO[..n]);
    }

    // Calling again immediately succeeds with the rest of the buffer
    let mut buf = [0; 32];
    let mut t = task::spawn(file.read(&mut buf));
    let n = assert_ready_ok!(t.poll());
    assert_eq!(n, 10);
    assert_eq!(&buf[..n], &HELLO[4..]);

    assert_eq!(0, pool::len());
}

#[test]
fn read_with_bigger_buf() {
    let (mock, file) = sys::File::mock();
    mock.read(&HELLO[..4]).read(&HELLO[4..]);

    let mut file = File::from_std(file);

    {
        let mut buf = [0; 4];
        let mut t = task::spawn(file.read(&mut buf));
        assert_pending!(t.poll());
    }

    pool::run_one();

    {
        let mut buf = [0; 32];
        let mut t = task::spawn(file.read(&mut buf));
        let n = assert_ready_ok!(t.poll());
        assert_eq!(n, 4);
        assert_eq!(&buf[..n], &HELLO[..n]);
    }

    // Calling again immediately succeeds with the rest of the buffer
    let mut buf = [0; 32];
    let mut t = task::spawn(file.read(&mut buf));

    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());

    let n = assert_ready_ok!(t.poll());
    assert_eq!(n, 10);
    assert_eq!(&buf[..n], &HELLO[4..]);

    assert_eq!(0, pool::len());
}

#[test]
fn read_err_then_read_success() {
    let (mock, file) = sys::File::mock();
    mock.read_err().read(&HELLO);

    let mut file = File::from_std(file);

    {
        let mut buf = [0; 32];
        let mut t = task::spawn(file.read(&mut buf));
        assert_pending!(t.poll());

        pool::run_one();

        assert_ready_err!(t.poll());
    }

    {
        let mut buf = [0; 32];
        let mut t = task::spawn(file.read(&mut buf));
        assert_pending!(t.poll());

        pool::run_one();

        let n = assert_ready_ok!(t.poll());

        assert_eq!(n, HELLO.len());
        assert_eq!(&buf[..n], HELLO);
    }
}

#[test]
fn open_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));

    assert_eq!(0, pool::len());
    assert_ready_ok!(t.poll());

    assert_eq!(1, mock.remaining());
    assert_eq!(1, pool::len());

    pool::run_one();

    assert_eq!(0, mock.remaining());
    assert!(!t.is_woken());

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());
}

#[test]
fn flush_while_idle() {
    let (_mock, file) = sys::File::mock();

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());
}

#[test]
fn read_with_buffer_larger_than_max() {
    // Chunks
    let chunk_a = 16 * 1024;
    let chunk_b = chunk_a * 2;
    let chunk_c = chunk_a * 3;
    let chunk_d = chunk_a * 4;

    assert_eq!(chunk_d / 1024, 64);

    let mut data = vec![];
    for i in 0..(chunk_d - 1) {
        data.push((i % 151) as u8);
    }

    let (mock, file) = sys::File::mock();
    mock.read(&data[0..chunk_a])
        .read(&data[chunk_a..chunk_b])
        .read(&data[chunk_b..chunk_c])
        .read(&data[chunk_c..]);

    let mut file = File::from_std(file);

    let mut actual = vec![0; chunk_d];
    let mut pos = 0;

    while pos < data.len() {
        let mut t = task::spawn(file.read(&mut actual[pos..]));

        assert_pending!(t.poll());
        pool::run_one();
        assert!(t.is_woken());

        let n = assert_ready_ok!(t.poll());
        assert!(n <= chunk_a);

        pos += n;
    }

    assert_eq!(mock.remaining(), 0);
    assert_eq!(data, &actual[..data.len()]);
}

#[test]
fn write_with_buffer_larger_than_max() {
    // Chunks
    let chunk_a = 16 * 1024;
    let chunk_b = chunk_a * 2;
    let chunk_c = chunk_a * 3;
    let chunk_d = chunk_a * 4;

    assert_eq!(chunk_d / 1024, 64);

    let mut data = vec![];
    for i in 0..(chunk_d - 1) {
        data.push((i % 151) as u8);
    }

    let (mock, file) = sys::File::mock();
    mock.write(&data[0..chunk_a])
        .write(&data[chunk_a..chunk_b])
        .write(&data[chunk_b..chunk_c])
        .write(&data[chunk_c..]);

    let mut file = File::from_std(file);

    let mut rem = &data[..];

    let mut first = true;

    while !rem.is_empty() {
        let mut task = task::spawn(file.write(rem));

        if !first {
            assert_pending!(task.poll());
            pool::run_one();
            assert!(task.is_woken());
        }

        first = false;

        let n = assert_ready_ok!(task.poll());

        rem = &rem[n..];
    }

    pool::run_one();

    assert_eq!(mock.remaining(), 0);
}

#[test]
fn write_twice_before_dispatch() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).write(FOO);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.write(FOO));
    assert_pending!(t.poll());

    assert_eq!(pool::len(), 1);
    pool::run_one();

    assert!(t.is_woken());

    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.flush());
    assert_pending!(t.poll());

    assert_eq!(pool::len(), 1);
    pool::run_one();

    assert!(t.is_woken());
    assert_ready_ok!(t.poll());
}

#[test]
fn incomplete_read_followed_by_write() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO)
        .seek_current_ok(-(HELLO.len() as i64), 0)
        .write(FOO);

    let mut file = File::from_std(file);

    let mut buf = [0; 32];

    let mut t = task::spawn(file.read(&mut buf));
    assert_pending!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.write(FOO));
    assert_ready_ok!(t.poll());

    assert_eq!(pool::len(), 1);
    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());
}

#[test]
fn incomplete_partial_read_followed_by_write() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO).seek_current_ok(-10, 0).write(FOO);

    let mut file = File::from_std(file);

    let mut buf = [0; 32];
    let mut t = task::spawn(file.read(&mut buf));
    assert_pending!(t.poll());

    pool::run_one();

    let mut buf = [0; 4];
    let mut t = task::spawn(file.read(&mut buf));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.write(FOO));
    assert_ready_ok!(t.poll());

    assert_eq!(pool::len(), 1);
    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());
}

#[test]
fn incomplete_read_followed_by_flush() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO)
        .seek_current_ok(-(HELLO.len() as i64), 0)
        .write(FOO);

    let mut file = File::from_std(file);

    let mut buf = [0; 32];

    let mut t = task::spawn(file.read(&mut buf));
    assert_pending!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.write(FOO));
    assert_ready_ok!(t.poll());

    pool::run_one();
}

#[test]
fn incomplete_flush_followed_by_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).write(FOO);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    let n = assert_ready_ok!(t.poll());
    assert_eq!(n, HELLO.len());

    let mut t = task::spawn(file.flush());
    assert_pending!(t.poll());

    // TODO: Move under write
    pool::run_one();

    let mut t = task::spawn(file.write(FOO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_ok!(t.poll());
}

#[test]
fn read_err() {
    let (mock, file) = sys::File::mock();
    mock.read_err();

    let mut file = File::from_std(file);

    let mut buf = [0; 1024];
    let mut t = task::spawn(file.read(&mut buf));

    assert_pending!(t.poll());

    pool::run_one();
    assert!(t.is_woken());

    assert_ready_err!(t.poll());
}

#[test]
fn write_write_err() {
    let (mock, file) = sys::File::mock();
    mock.write_err();

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.write(FOO));
    assert_ready_err!(t.poll());
}

#[test]
fn write_read_write_err() {
    let (mock, file) = sys::File::mock();
    mock.write_err().read(HELLO);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    let mut buf = [0; 1024];
    let mut t = task::spawn(file.read(&mut buf));

    assert_pending!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.write(FOO));
    assert_ready_err!(t.poll());
}

#[test]
fn write_read_flush_err() {
    let (mock, file) = sys::File::mock();
    mock.write_err().read(HELLO);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    let mut buf = [0; 1024];
    let mut t = task::spawn(file.read(&mut buf));

    assert_pending!(t.poll());

    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_err!(t.poll());
}

#[test]
fn write_seek_write_err() {
    let (mock, file) = sys::File::mock();
    mock.write_err().seek_start_ok(0);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    {
        let mut t = task::spawn(file.seek(SeekFrom::Start(0)));
        assert_pending!(t.poll());
    }

    pool::run_one();

    let mut t = task::spawn(file.write(FOO));
    assert_ready_err!(t.poll());
}

#[test]
fn write_seek_flush_err() {
    let (mock, file) = sys::File::mock();
    mock.write_err().seek_start_ok(0);

    let mut file = File::from_std(file);

    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    pool::run_one();

    {
        let mut t = task::spawn(file.seek(SeekFrom::Start(0)));
        assert_pending!(t.poll());
    }

    pool::run_one();

    let mut t = task::spawn(file.flush());
    assert_ready_err!(t.poll());
}

#[test]
fn sync_all_ordered_after_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).sync_all();

    let mut file = File::from_std(file);
    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.sync_all());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_ready_ok!(t.poll());
}

#[test]
fn sync_all_err_ordered_after_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).sync_all_err();

    let mut file = File::from_std(file);
    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.sync_all());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_ready_err!(t.poll());
}

#[test]
fn sync_data_ordered_after_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).sync_data();

    let mut file = File::from_std(file);
    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.sync_data());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_ready_ok!(t.poll());
}

#[test]
fn sync_data_err_ordered_after_write() {
    let (mock, file) = sys::File::mock();
    mock.write(HELLO).sync_data_err();

    let mut file = File::from_std(file);
    let mut t = task::spawn(file.write(HELLO));
    assert_ready_ok!(t.poll());

    let mut t = task::spawn(file.sync_data());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_pending!(t.poll());

    assert_eq!(1, pool::len());
    pool::run_one();

    assert!(t.is_woken());
    assert_ready_err!(t.poll());
}

#[test]
fn open_set_len_ok() {
    let (mock, file) = sys::File::mock();
    mock.set_len(123);

    let file = File::from_std(file);
    let mut t = task::spawn(file.set_len(123));

    assert_pending!(t.poll());
    assert_eq!(1, mock.remaining());

    pool::run_one();
    assert_eq!(0, mock.remaining());

    assert!(t.is_woken());
    assert_ready_ok!(t.poll());
}

#[test]
fn open_set_len_err() {
    let (mock, file) = sys::File::mock();
    mock.set_len_err(123);

    let file = File::from_std(file);
    let mut t = task::spawn(file.set_len(123));

    assert_pending!(t.poll());
    assert_eq!(1, mock.remaining());

    pool::run_one();
    assert_eq!(0, mock.remaining());

    assert!(t.is_woken());
    assert_ready_err!(t.poll());
}

#[test]
fn partial_read_set_len_ok() {
    let (mock, file) = sys::File::mock();
    mock.read(HELLO)
        .seek_current_ok(-14, 0)
        .set_len(123)
        .read(FOO);

    let mut buf = [0; 32];
    let mut file = File::from_std(file);

    {
        let mut t = task::spawn(file.read(&mut buf));
        assert_pending!(t.poll());
    }

    pool::run_one();

    {
        let mut t = task::spawn(file.set_len(123));

        assert_pending!(t.poll());
        pool::run_one();
        assert_ready_ok!(t.poll());
    }

    let mut t = task::spawn(file.read(&mut buf));
    assert_pending!(t.poll());
    pool::run_one();
    let n = assert_ready_ok!(t.poll());

    assert_eq!(n, FOO.len());
    assert_eq!(&buf[..n], FOO);
}
