use jaded::{ConversionError, JavaError, Parser};
use jaded_derive::FromJava;
use std::{fs::File, io::Read};

#[derive(Debug, FromJava, PartialEq)]
struct Marker;

#[derive(Debug, FromJava, PartialEq)]
struct Parent {
    i: i32,
    foo: String,
}

#[derive(Debug, FromJava, PartialEq)]
struct Child {
    i: i32,
    foo: String,
    bar: String,
    arr: Vec<f64>,
}

#[derive(Debug, FromJava)]
struct NewChild(Child);

#[derive(Debug, FromJava, PartialEq)]
struct Custom {
    i: i32,
    #[jaded(extract(utils::read_int))]
    j: i32,
    #[jaded(extract(utils::read_str))]
    msg: String,
}

#[derive(Debug, FromJava, PartialEq)]
struct CustomChild {
    i: i32,
    #[jaded(extract(utils::read_int, 1))]
    foo: i32,
    #[jaded(extract(utils::read_int))]
    j: i32,
    #[jaded(extract(utils::read_str))]
    msg: String,
}

#[derive(Debug, FromJava)]
struct Poly<T> {
    #[jaded(from = "Collection<T>")]
    names: Vec<T>,
}

#[derive(Debug, FromJava)]
enum Collection<T> {
    #[jaded(class = "java.util.ArrayList")]
    ArrayList(#[jaded(extract(utils::read_list))] Vec<T>),
    #[jaded(class = "java.util.Arrays$ArrayList")]
    ArraysList(#[jaded(field = "a")] Vec<T>),
    #[jaded(class = "java.util.LinkedList")]
    LinkedList(#[jaded(extract(utils::read_list))] Vec<T>),
    #[jaded(class = "java.util.Stack")]
    Stack(
        #[jaded(field = "elementCount")] i32,
        #[jaded(field = "elementData")] Vec<Option<T>>,
    ),
    #[jaded(class = "java.util.Collections$EmptyList")]
    Empty,
    #[jaded(class = "java.util.Vector")]
    Vector(
        #[jaded(field = "elementCount")] i32,
        #[jaded(field = "elementData")] Vec<Option<T>>,
    ),
}

#[derive(Debug, FromJava)]
struct SampleRecord {
    i: i32,
    s: String,
}

impl<T> From<Collection<T>> for Vec<T> {
    fn from(c: Collection<T>) -> Self {
        match c {
            Collection::ArrayList(v) | Collection::ArraysList(v) | Collection::LinkedList(v) => v,
            Collection::Stack(c, v) | Collection::Vector(c, v) => {
                v.into_iter().take(c as usize).map(|e| e.unwrap()).collect()
            }
            Collection::Empty => vec![],
        }
    }
}

mod utils {
    use jaded::{AnnotationIter, ConversionResult, FromJava};
    pub fn read_list<T: FromJava>(anno: &mut AnnotationIter) -> ConversionResult<Vec<T>> {
        (0..anno.read_i32()? as usize)
            .map(|_| anno.read_object_as())
            .collect()
    }

    pub fn read_int(anno: &mut AnnotationIter) -> ConversionResult<i32> {
        anno.read_i32()
    }

    pub fn read_str(anno: &mut AnnotationIter) -> ConversionResult<String> {
        anno.read_object_as()
    }
}

fn source(path: &str) -> Parser<impl Read> {
    let src = File::open(path).unwrap();
    Parser::new(src).unwrap()
}

#[test]
fn read_marker() {
    // A unit struct can be read from anything...
    let mut parser = source("../res/extended_pojo.obj");
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    let mut parser = source("../res/custom_write.obj");
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    let mut parser = source("../res/polymorphism.obj");
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    assert_eq!(parser.read_as::<Marker>().unwrap(), Marker);
    let mut parser = source("../res/null.obj");
    // ...that isn't null
    assert!(matches!(
        parser.read_as::<Marker>().unwrap_err(),
        JavaError::ConvertError(ConversionError::NullPointerException)
    ));
}

#[test]
fn read_child() {
    let mut parser = source("../res/extended_pojo.obj");
    let child = parser.read_as::<Child>().unwrap();
    assert_eq!(
        child,
        Child {
            i: 1,
            bar: "helloWorld".into(),
            foo: "bar".into(),
            arr: vec![1.2, 2.3, 3.4, 4.5]
        }
    );
}

#[test]
fn read_new_child() {
    let mut parser = source("../res/extended_pojo.obj");
    let new = parser.read_as::<NewChild>().unwrap();
    assert_eq!(
        new.0,
        Child {
            i: 1,
            bar: "helloWorld".into(),
            foo: "bar".into(),
            arr: vec![1.2, 2.3, 3.4, 4.5],
        }
    );
}

#[test]
fn read_parent() {
    let mut parser = source("../res/pojo.obj");
    let parent = parser.read_as::<Parent>().unwrap();
    assert_eq!(
        parent,
        Parent {
            i: 13,
            foo: "bar".into()
        }
    );
}

#[test]
fn read_custom() {
    let mut parser = source("../res/custom_write.obj");
    let custom = parser.read_as::<Custom>().unwrap();
    assert_eq!(
        custom,
        Custom {
            i: 1,
            j: 2,
            msg: "helloWorld".into()
        }
    );
    let custom = parser.read_as::<CustomChild>().unwrap();
    assert_eq!(
        custom,
        CustomChild {
            i: 15,
            foo: 211,
            j: 5,
            msg: "fooBar".into()
        }
    );
}

#[test]
fn read_null() {
    let mut parser = source("../res/null.obj");
    let custom = parser.read_as::<Option<Custom>>().unwrap();
    assert_eq!(custom, None);
}

#[test]
fn read_collections() {
    let mut parser = source("../res/polymorphism.obj");
    let expected = ["foo", "foo", "bar", "buzz", "fizz"];

    let list = parser.read_as::<Poly<String>>().expect("Arrays.asList");
    assert_eq!(&list.names, &expected);
    let list = parser.read_as::<Poly<String>>().expect("ArrayList");
    assert_eq!(&list.names, &expected);
    let list = parser.read_as::<Poly<String>>().expect("LinkedList");
    assert_eq!(&list.names, &expected);
    let list = parser.read_as::<Poly<String>>().expect("Stack");
    assert_eq!(&list.names, &expected);
    let list = parser.read_as::<Poly<String>>().expect("empty");
    assert!(&list.names.is_empty());
    let list = parser.read_as::<Poly<String>>().expect("Vector");
    assert_eq!(&list.names, &expected);
}

#[test]
fn read_record() {
    let mut parser = source("../res/sample_record.obj");
    let record = parser.read_as::<SampleRecord>().expect("sample record");
    assert_eq!(record.i, 42);
    assert_eq!(record.s, "helloWorld");
}
