//#####################################################################
// Copyright 2020.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################

#include <Core/Arrays/ARRAY.h>
#include <Core/Data_Structures/HASHTABLE.h>
#include <Core/Data_Structures/TUPLE.h>
#include <Core/Log/LOG.h>
#include <Core/Vectors/VECTOR.h>

using namespace PhysBAM;

// them quickly to get a general sense for what is in them and how to interface
// with them.  This is also a good time to familiarize yourself with lambda
// functions.

int TASK=-1;

template<class A>
void Test(const A& a,const std::string& sol)
{
    if(TASK>=0) std::cout<<"TASK: "<<TASK<<std::endl;
    std::cout<<"should be: "<<sol<<std::endl;
    std::cout<<"computed : "<<a<<std::endl;
    if(LOG::sprintf("%P",a)==sol) std::cout<<"SAME\n"<<std::endl;
    else std::cout<<"DIFFERENT\n"<<std::endl;
    TASK=-1;
}

void main2();
int main(int argc, char* argv[])
{
    // Lambda functions give us a way to make local functions.  
    auto f_0=[](int a,int b){return a+b*2;};
    Test(f_0(1,2),"5");
    Test(f_0(10,7),"24");

    int s = 7;
    auto f_1=[&s](int b){s+=b;}; // The &s means s is "captured" by reference
    Test(s,"7");
    f_1(2);
    Test(s,"9");
    f_1(7);
    Test(s,"16");

    s=7;
    auto f_2=[s](int b){return s+b;}; // The s means s is "captured" by value
    Test(s,"7");
    Test(f_2(1),"8");
    Test(s,"7");
    s+=4;
    Test(f_2(1),"8");
    Test(s,"11");

    VECTOR<std::string,4> a0={"apple","cat","dog","banana"};
    std::sort(a0.begin(),a0.end(),
        [](const std::string& a,const std::string& b)
        {
            if(a.size()<b.size()) return true;
            if(a.size()>b.size()) return false;
            return a<b;
        }); // Sort by size first.
    Test(a0,"(cat dog apple banana)");

    // First, lets start exploring how to use pair.

    TASK=2;
    pair<std::string,int> p;
    // Use one statement to initialize p.
    // TODO
    Test(p,"(cat 4)");

    TASK=3;
    tuple<std::string,pair<double,std::string>,ARRAY<int> > t;
    // Use one statement to initialize t.
    // TODO
    Test(t,"(dog (1.2 sun) (2 5 7 9))");

    ARRAY<pair<int,float> > a1(4,init_new,{2,3.1f});
    Test(a1,"((2 3.1) (2 3.1) (2 3.1) (2 3.1))");
    
    ARRAY<pair<int,float> > a2(4);
    Test(a2,"((0 0) (0 0) (0 0) (0 0))");

    TASK=4;
    // Change the second int value in a1.
    // TODO
    Test(a1,"((2 3.1) (7 3.1) (2 3.1) (2 3.1))");

    TASK=5;
    // Update one of the strings in t.
    // TODO
    Test(t,"(dog (1.2 pen) (2 5 7 9))");

    main2();
    return 0;
}

// There is a template-friendly to get access to members numerically.

template<int n>
struct S1
{
    std::string f(const pair<std::string,int>& p)
    {
        return "blame" + get<n>(p);
    }
};

void main2()
{
    pair<std::string,int> p("less",1);
    // Make sure that you understand why these two work the way they do.
    Test(S1<0>().f(p),"blameless");
    Test(S1<1>().f(p),"lame");

    // It's probably not what you think it is.
    pair<int,int> p0();
    Test(sizeof(p0())==sizeof(int)*2,"1");

    // Note that pairs and triples are comparable, which is useful for sorting.
    pair<std::string,int> q("more",0);
    Test(q<p,"0");
    Test(q>p,"1");
    Test(q<=p,"0");
    Test(q>=p,"1");
    Test(q==p,"0");
    Test(q!=p,"1");

    HASHTABLE<int> h0;
    Test(h0,"()");

    TASK=6;
    // Add entry to h0.
    // TODO
    Test(h0,"(7)");

    TASK=7;
    // Add entry to h0.
    // TODO
    Test(h0,"(7 3)");

    TASK=8;
    int s=0;
    // Get number of entries in hashtable.
    s=h0.Size();
    Test(s,"2");
    
    TASK=9;
    // Remove entry for h0.
    // TODO
    Test(h0,"(3)");

    TASK=10;
    ARRAY<int> a0={2,4,8,9,1,20};
    // add these to h0.
    // TODO
    Test(h0,"(2 9 3 1 4 20 8)");

    TASK=11;
    HASHTABLE<std::string,ARRAY<int> > h1;
    // add entry to h1.
    // TODO
    Test(h1,"(cat:(1 3 4 8))");

    TASK=12;
    VECTOR<std::string,3> v0("apple","banana","carrot");
    ARRAY<ARRAY<int> > a1={{},{1,2},{4,5,8}};
    // add entries to h1.
    // TODO
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");

    auto h2=h1;
    
    // In the following tasks, you will need to implement the lambda function.
    // Your solution should only make one function call to the hashtable.
    TASK=13;
    // Test to see if x is in h0.
    auto l0=[&h0](int x)
    {
        (void)h0; // This is a trick to "explicitly not use" a variable
        return true;
        // TODO
    };
    Test(l0(1),"1");
    Test(l0(3),"1");
    Test(l0(5),"0");
    Test(l0(10),"0");
    Test(l0(20),"1");

    TASK=14;
    // If h1 has an entry for s, append n to the corresponding array.  If h1
    // does not have an entry for s, insert the key and assign it an array with
    // just the entry n.
    auto l1=[&h1](const std::string& s, int n)
    {
        (void)h1;
        return ARRAY<int>();
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    Test(l1("apple",7),"(7)");
    Test(l1("egg",8),"(8)");
    Test(l1("banana",9),"(1 2 9)");
    Test(l1("egg",3),"(8 3)");
    Test(h1,"(apple:(7) banana:(1 2 9) carrot:(4 5 8) cat:(1 3 4 8) egg:(8 3))");
    h1=h2;
    
    TASK=15;
    // If h1 has an entry for s, append n to the corresponding array.  If h1
    // does not have an entry for s, leave the hashtable unchanged.
    auto l2=[&h1](const std::string& s, int n)
    {
        (void)h1;
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    l2("apple",0);
    Test(h1,"(apple:(0) banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    l2("egg",8);
    Test(h1,"(apple:(0) banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    l2("banana",1);
    Test(h1,"(apple:(0) banana:(1 2 1) carrot:(4 5 8) cat:(1 3 4 8))");
    l2("egg",3);
    Test(h1,"(apple:(0) banana:(1 2 1) carrot:(4 5 8) cat:(1 3 4 8))");
    h1=h2;

    TASK=16;
    // If h1 has an entry for s, return the array stored there.  Otherwise,
    // return the empty array.  Never modify h1.
    auto l3=[&h1](const std::string& s)
    {
        (void)h1;
        return ARRAY<int>();
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    Test(l3("apple"),"()");
    Test(l3("egg"),"()");
    Test(l3("banana"),"(1 2)");
    Test(l3("egg"),"()");
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    h1=h2;

    TASK=17;
    // If h1 has an entry for s, return the array stored there.  Otherwise,
    // return a.  Never modify h1.
    auto l4=[&h1](const std::string& s,const ARRAY<int>& a)
    {
        (void)h1;
        return ARRAY<int>();
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    Test(l4("apple",{0,1,2}),"()");
    Test(l4("egg",{3,4,5}),"(3 4 5)");
    Test(l4("banana",{6,7,8}),"(1 2)");
    Test(l4("egg",{9,10,11}),"(9 10 11)");
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    h1=h2;

    TASK=18;
    HASHTABLE<int,int> h3;
    int a2[]={0,1,2,3,7,1,1,0,3,3,3,3,3,3,3,5,10,0,1,-8};
    // Count the number of occurrences of each number.
    auto l5=[&h3](int x)
    {
        (void)h3;
        // TODO
    };
    for(auto n:a2) l5(n);
    Test(h3,"(1:4 2:1 10:1 5:1 0:3 -8:1 7:1 3:8)");

    TASK=19;
    // If h1 has an entry for s, return 3 but do not modify the hashtable.
    // Otherwise, insert the key for s with value a and return 7.
    auto l6=[&h1](const std::string& s, const ARRAY<int>& a)
    {
        (void)h1;
        return 0;
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    Test(l6("apple",{0,1,2}),"3");
    Test(l6("egg",{3,4,5}),"7");
    Test(l6("banana",{6,7,8}),"3");
    Test(l6("egg",{9,10,11}),"3");
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8) egg:(3 4 5))");
    h1=h2;

    TASK=20;
    // If h1 has an entry for s, set its value to a, and return 3.
    // Otherwise, insert the key for s with value a and return 7.
    auto l7=[&h1](const std::string& s, const ARRAY<int>& a)
    {
        (void)h1;
        return 0;
        // TODO
    };
    Test(h1,"(apple:() banana:(1 2) carrot:(4 5 8) cat:(1 3 4 8))");
    Test(l7("apple",{0,1,2}),"3");
    Test(l7("egg",{3,4,5}),"7");
    Test(l7("banana",{6,7,8}),"3");
    Test(l7("egg",{9,10,11}),"3");
    Test(h1,"(apple:(0 1 2) banana:(6 7 8) carrot:(4 5 8) cat:(1 3 4 8) egg:(9 10 11))");
    h1=h2;

    TASK=21;
    // Append a 13 to the end of all of the values stored in the hashtable.
    // Note that hashtables support range-based for.
    // TODO
    Test(h1,"(apple:(13) banana:(1 2 13) carrot:(4 5 8 13) cat:(1 3 4 8 13))");

    TASK=22;
    // Remove everything from the hashtable.
    h1.Remove_All();
    Test(h1,"()");
}
