#![feature(test)]

extern crate test;
extern crate keeshond_datapack;

use keeshond_datapack::{ReadSeek, DataId, DataObject, DataError, DataStore, DataMultistore, TrustPolicy, DataHandle, LoadErrorMode};
use keeshond_datapack::source::{PackageError, Source, SourceManager, TrustLevel};

use test::Bencher;

use std::io::{Cursor, Read};
use std::cell::RefCell;
use std::rc::Rc;

const NUM_ITER : usize = 1_000;

const TEST_ITEMS : [&'static str; 8] = [
    "item1",
    "item2",
    "item3",
    "item4",
    "directory/anotherdirectory/subitem1",
    "directory/anotherdirectory/subitem2",
    "directory/anotherdirectory/subitem3",
    "directory/anotherdirectory/subitem4",
];


struct TestSourceIter
{
    items : Vec<&'static str>,
    index : usize
}

impl<'a> TestSourceIter
{
    fn new() -> TestSourceIter
    {
        let items = TEST_ITEMS.to_vec();
        
        TestSourceIter
        {
            items,
            index : 0
        }
    }
}

impl Iterator for TestSourceIter
{
    type Item = Result<String, PackageError>;
    
    fn next(&mut self) -> Option<Result<String, PackageError>>
    {
        if self.index < self.items.len()
        {
            let pathname = self.items[self.index].to_string();
            
            self.index += 1;
        
            return Some(Ok(pathname));
        }
        
        None
    }
}

pub struct TestSource
{
    
}

impl TestSource
{
    pub fn new() -> TestSource
    {
        TestSource
        {

        }
    }
}

impl Source for TestSource
{
    fn get_uri(&self) -> &str
    {
        "null"
    }
    fn has_package(&self, package_name : &str) -> bool
    {
        package_name == "foopackage"
    }
    fn list_packages(&mut self) -> Vec<String>
    {
        vec!["foopackage".to_string()]
    }
    fn read_file(&mut self, _full_pathname : &str) -> Result<Box<dyn ReadSeek>, PackageError>
    {
        Ok(Box::new(Cursor::new("12345678")))
    }
    fn iter_entries(&mut self, _package_name : &str, _type_name : &str) -> Box<dyn Iterator<Item = Result<String, PackageError>>>
    {
        Box::new(TestSourceIter::new())
    }
    fn trust_level(&self, _package_name: &str) -> TrustLevel { TrustLevel::TrustedSource }
}


////////////////////////////////////////////////////////////////////////////////


struct TextData
{
    text : String
}

impl DataObject for TextData
{
    fn folder_name() -> &'static str where Self : Sized
    {
        "text"
    }
    fn trust_policy() -> TrustPolicy { TrustPolicy::UntrustedOk }
    fn want_file(_pathname : &str) -> bool where Self : Sized
    {
        true
    }
    fn from_io(mut reader: Box<dyn ReadSeek>, _full_pathname : &str, _source : &mut Box<dyn Source>) -> Result<Self, DataError> where Self : Sized
    {
        let mut text = String::new();
        
        let result = reader.read_to_string(&mut text);
        
        if result.is_err()
        {
            return Err(DataError::BadData("Couldn't read string".to_string()));
        }
        
        Ok(TextData{ text })
    }
}


////////////////////////////////////////////////////////////////////////////////


fn make_store() -> DataStore<TextData>
{
    let source = TestSource::new();
    let source_manager = Rc::new(RefCell::new(SourceManager::new()));
    source_manager.borrow_mut().add_source(Box::new(source));
    
    let mut store = DataStore::<TextData>::new(source_manager);
    
    store.load_package("foopackage").expect("Failed to load package.");
    
    store
}

fn make_multistore() -> DataMultistore
{
    let source = TestSource::new();
    let source_manager = Rc::new(RefCell::new(SourceManager::new()));
    source_manager.borrow_mut().add_source(Box::new(source));

    let multistore = DataMultistore::new(source_manager);

    multistore.store_mut::<TextData>().load_package("foopackage").expect("Failed to load package.");

    multistore
}

fn test_ids(store : &DataStore<TextData>) -> Vec<DataId>
{
    let mut ids = Vec::new();
    
    for name in &TEST_ITEMS
    {
        ids.push(store.get_id("foopackage", name).unwrap());
    }
    
    ids
}

fn test_handles() -> Vec<DataHandle<TextData>>
{
    let mut handles = Vec::new();

    for name in &TEST_ITEMS
    {
        handles.push(DataHandle::new("foopackage", name));
    }

    handles
}

#[bench]
fn bench_create(bencher : &mut Bencher)
{
    bencher.iter(||
    {
        make_store();
    });
}

#[bench]
fn bench_get_id(bencher : &mut Bencher)
{
    let store = make_store();
    
    bencher.iter(||
    {
        for _ in 0..NUM_ITER
        {
            for name in &TEST_ITEMS
            {
                store.get_id("foopackage", name).unwrap();
            }
        }
    });
}

#[bench]
fn bench_get_handle(bencher : &mut Bencher)
{
    let mut store = make_store();
    let handles = test_handles();

    let mut count = 0;

    for handle in handles.iter()
    {
        handle.resolve(&mut store, LoadErrorMode::Fatal);
    }

    bencher.iter(||
    {
        for _ in 0..NUM_ITER
        {
            for handle in handles.iter()
            {
                if let Some(item) = handle.get(&mut store, LoadErrorMode::Fatal)
                {
                    count += item.text.len();
                }
            }
        }
    });
}

#[bench]
fn bench_get_data(bencher : &mut Bencher)
{
    let store = make_store();
    let ids = test_ids(&store);
    
    let mut count = 0;
    
    bencher.iter(||
    {
        for _ in 0..NUM_ITER
        {
            for id in ids.iter()
            {
                if let Some(item) = store.get(*id)
                {
                    count += item.text.len();
                }
            }
        }
    });
}

#[bench]
fn bench_get_data_mut(bencher : &mut Bencher)
{
    let mut store = make_store();
    let ids = test_ids(&store);
    
    let mut count = 0;
    
    bencher.iter(||
    {
        for _ in 0..NUM_ITER
        {
            for id in ids.iter()
            {
                if let Some(item) = store.get_mut(*id)
                {
                    count += item.text.len();
                }
            }
        }
    });
}

#[bench]
fn bench_get_store(bencher : &mut Bencher)
{
    let multistore = make_multistore();
    let ids = test_ids(&multistore.store::<TextData>());

    let mut count = 0;

    bencher.iter(||
    {
        for _ in 0..NUM_ITER
        {
            for id in ids.iter()
            {
                if let Some(item) = multistore.store::<TextData>().get(*id)
                {
                    count += item.text.len();
                }
            }
        }
    });
}
