/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.index;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import net.ranides.assira.collection.sets.RBTreeMultiSet;
import net.ranides.assira.generic.CompareUtils;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class IQueryMapTreeTest {

    @Test
    public void testSetIndex() {
        
        IQueryMap<Person> db = new IQueryMapTree<>();
        
        db.index("name", (a,b) -> CompareUtils.cmp(a.name, b.name));
        db.index("surname", (a,b) -> CompareUtils.cmp(a.surname, b.surname));
        db.index("age", (a,b) -> CompareUtils.cmp(a.age, b.age));
        
        putData(db);
        
        assertQueryEquals("[{Helene C 19}, {Helene C 23}]", db.find()
            .eq("name", new Person("Helene", "", 0))
        );
        assertQueryEquals("[{Tom B 23}, {Helene C 23}, {Andrew S 23}]", db.find()
            .eq("age", new Person("", "", 23))
        );
        assertQueryEquals("[{Helene C 23}]", db.find()
            .eq("name", new Person("Helene", "", 0))
            .eq("age", new Person("", "", 23))
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}, {George T 17}]", db.find()
            .eq("age", new Person("", "", 17))
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}]", db.find()
            .eq("surname", new Person("", "A", 0))
            .eq("age", new Person("", "", 17))
        );
        assertQueryEquals("[{Joe A 20}, {Tom B 23}, {Helene C 19}, {Helene C 23}, {Andrew S 23}]", db.find()
            .gt("age", new Person("", "", 17))
        );
        assertQueryEquals("[]", db.find()
            .gt("age", new Person("", "", 40))
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}, {George T 17}]", db.find()
            .lt("age", new Person("", "", 18))
        );
        assertQueryEquals("[]", db.find()
            .lt("age", new Person("", "", 17))
        );
        assertQueryEquals("[{Helene C 19}]", db.find()
            .eq("name", new Person("Helene", "", 23))
            .lt("age", new Person("Helene", "", 23))
        );
    }

    @Test
    public void testReindex() {
        IQueryMap<Person> db = new IQueryMapTree<>();

        db.index("name", a -> a.name);
        db.index("surname", (a,b) -> CompareUtils.cmp(a.surname, b.surname));
        List<Person> data = putData(db);

        assertQueryEquals("[{Helene C 19}, {Helene C 23}]", db.find().eq("name", "Helene"));
        assertQueryEquals("[{Joe A 20}, {Peter A 17}, {Tom A 17}]", db.find().eq("surname", new Person("", "A", 0)));

//      0  new Person("Tom", "A", 17)
//      1  new Person("Tom", "B", 23)
//      2  new Person("Joe", "A", 20)
//      3  new Person("Helene", "C", 19)
//      4  new Person("Andrew", "S", 23)
//      5  new Person("Helene", "C", 23)
//      6  new Person("George", "T", 17)
//      7  new Person("Peter", "A", 17)

        data.get(4).name = "Helene"; // new Person("Andrew", "S", 23)
        data.get(5).name = "Catherine"; // new Person("Helene", "C", 23);

        data.get(0).surname = "X"; // new Person("Tom", "A", 17)
        data.get(6).surname = "A"; // new Person("George", "T", 17)
        data.get(7).surname = "X"; // new Person("Peter", "A", 17)

        // a bit broken indexes
        assertQueryNotEquals("[{Helene C 19}, {Helene S 23}]", db.find().eq("name", "Helene"));
        assertQueryNotEquals("[{George A 17}, {Joe A 20}]", db.find().eq("surname", new Person("", "A", 0)));

        // fixed indexes
        db.updateAll("name");
        db.updateAll("surname");

        assertQueryEquals("[{Helene C 19}, {Helene S 23}]", db.find().eq("name", "Helene"));
        assertQueryEquals("[{George A 17}, {Joe A 20}]", db.find().eq("surname", new Person("", "A", 0)));
    }

    @Test
    public void testMapIndex() {
        
        IQueryMap<Person> db = new IQueryMapTree<>();
        
        db.index("name", a -> a.name);
        db.index("surname", a -> a.surname);
        db.index("age", a -> a.age);
        
        putData(db);
        
        assertQueryEquals("[{Helene C 19}, {Helene C 23}]", db.find()
            .eq("name", "Helene")
        );
        assertQueryEquals("[{Tom B 23}, {Helene C 23}, {Andrew S 23}]", db.find()
            .eq("age", 23)
        );
        assertQueryEquals("[{Helene C 23}]", db.find()
            .eq("name", "Helene")
            .eq("age", 23)
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}, {George T 17}]", db.find()
            .eq("age", 17)
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}]", db.find()
            .eq("surname", "A")
            .eq("age", 17)
        );
        assertQueryEquals("[{Joe A 20}, {Tom B 23}, {Helene C 19}, {Helene C 23}, {Andrew S 23}]", db.find()
            .gt("age", 17)
        );
        assertQueryEquals("[]", db.find()
            .gt("age", 40)
        );
        assertQueryEquals("[{Peter A 17}, {Tom A 17}, {George T 17}]", db.find()
            .lt("age", 18)
        );
        assertQueryEquals("[]", db.find()
            .lt("age", 17)
        );
        assertQueryEquals("[{Helene C 19}]", db.find()
            .eq("name", "Helene")
            .lt("age", 23)
        );
    }
    
    @Test
    public void testMapIndexOptional() {
        
        IQueryMap<Person> db = new IQueryMapTree<>();
        
        db.index("name", a -> a.name);
        db.index("surname", a -> a.surname);
        db.index("age", a -> a.age);
        putData(db);
        db.put(new Person(null, "A", 41));
        db.put(new Person("Andrew", null, 42));
        db.put(new Person(null, null, 43));
        
        assertEquals("[{Tom A 17}, {Tom B 23}]", "" + db.find().eq("name", "Tom").sort().list());
        assertEquals("[{Andrew S 23}]","" + db.find().eq("surname", "S").sort().list());

        assertEquals("[{Tom A 17}, {Tom B 23}]", "" + db.find().eq("name", "Tom").sort().list());
        assertEquals("[{Andrew S 23}]", "" + db.find().eq("surname", "S").sort().list());

        assertEquals("[{null null 43}, {null A 41}, {Tom A 17}, {Tom B 23}]", "" + db.find().eq("name", true, "Tom").sort().list());
        assertEquals("[{null null 43}, {Andrew null 42}, {Andrew S 23}]", "" + db.find().eq("surname", true, "S").sort().list());

        assertEquals("[{null null 43}, {Andrew null 42}, {null A 41}]", "" + db.find()
            .eq("surname", true,"A")
            .eq("name", true, "Andrew")
            .sort().list()
        );
    }
    
    private static void assertQueryEquals(String expected, IQuery<Person> query) {
        assertEquals(expected, query.sort().list().toString());
    }

    private static void assertQueryNotEquals(String expected, IQuery<Person> query) {
        assertNotEquals(expected, query.sort().list().toString());
    }

    private static List<Person> putData(IQueryMap<Person> db) {
        List<Person> records = Arrays.asList(
            new Person("Tom", "A", 17),
            new Person("Tom", "B", 23),
            new Person("Joe", "A", 20),
            new Person("Helene", "C", 19),
            new Person("Andrew", "S", 23),
            new Person("Helene", "C", 23),
            new Person("George", "T", 17),
            new Person("Peter", "A", 17)
        );

        for(Person person : records) {
            db.put(person);
        }

        return records;
    }

    private static final class Person implements Comparable<Person> {
        
        public String name;
        public String surname;
        public int age;

        public Person(String name, String surname, int age) {
            this.name = name;
            this.surname = surname;
            this.age = age;
        }

        @Override
        public String toString() {
            return "{" + name + " " + surname + " " + age + "}";
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 47 * hash + Objects.hashCode(this.name);
            hash = 47 * hash + Objects.hashCode(this.surname);
            hash = 47 * hash + this.age;
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            return this == obj || ((obj instanceof Person) && 0 == compareTo((Person) obj));
        }
        
        @Override
        public int compareTo(Person that) {
            int c;
            c = CompareUtils.cmp(this.surname, that.surname);
            if(c!=0) {
                return c;
            }
            c = CompareUtils.cmp(this.name, that.name);
            if(c!=0) {
                return c;
            }
            c = CompareUtils.cmp(this.age, that.age);
            return c;
        }
        
    }
    
}
