/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "macro.h"
#include "arith.h"
#include "code.h"

#include <iostream>
#include <utility>
#include <vector>


enum eListIndexType
{
    LINDEX_NUMBERS,
    LINDEX_VECTOR
};


//=============================================================================

int CList::Write(FILE* f)
{
    int e = 0;
    for (int i = 0; i < count; i++)
        if (values[i].Write(f))
            e++;
    return e;
}

void CList::ToRequest(request*& x)
{
    static request* r = 0;
    free_all_requests(r);
    r = 0;
    request *s = 0, *t = 0;

    for (int i = 0; i < count; i++) {
        request* u;
        values[i].GetValue(u);
        u = clone_all_requests(u);

        if (s == 0)
            s = u;
        else if (t)
            t->next = u;

        t = u;
        while (t && t->next)
            t = t->next;
    }
    x = r = s;
}

void CList::SetSubValue(Value& v, int arity, Value* arg)
{
    if (!Check(1, v, arity, arg, tany, 1, tnumber))
        return;

    int n;
    arg[0].GetValue(n);

    if (n < 1 || n > count) {
        Error("List index [%d] is out of range (list is %d long)",
              n, count);
        return;
    }

    values[n - 1] = v;
}

//=============================================================================

void CList::Copy(const CList& v)
{
    count = v.count;
    capacity = v.capacity;
    values = new Value[capacity];

    for (int i = 0; i < count; i++)
        values[i] = v.values[i];
}

void CList::Move(CList&& v)
{
    count = v.count;
    capacity = v.capacity;

    values = v.values;
    v.values = nullptr;
}
void CList::Add(const Value& v)
{
    if (count == capacity) {
        auto* new_values = new Value[capacity += CHUNK];

        for (int ii = 0; ii < count; ii++)  // copy the existing values across
            new_values[ii] = values[ii];

        delete[] values;
        values = new_values;
    }

    values[count++] = v;  // add the new value
}

CList& CList::operator=(const CList& v)
{
    CleanUp();
    Copy(v);
    return *this;
}

CList::CList(const CList& v) :
    Content(tlist)
{
    Copy(v);
}

CList::CList(CList&& v) :
    Content(tlist)
{
    Move(std::move(v));
}

CList& CList::operator=(CList&& v)
{
    CleanUp();
    Move(std::move(v));
    return *this;
}

void CList::Print(void)
{
    std::cout << '[';
    for (int i = 0; i < count; i++) {
        values[i].Print();
        if (i != count - 1)
            std::cout << ',';
    }
    std::cout << ']';
}

void CList::Dump2(void)
{
    std::cout << "[\n";
    for (int i = 0; i < count; i++) {
        values[i].Dump(2);
        std::cout << ",\n";
    }
    std::cout << "]\n";
}

Content* CList::Clone(void)
{
    auto* list = new CList(*this);
    return list;
}

//=============================================================================

class ListFunction : public Function
{
public:
    ListFunction(const char* n) :
        Function(n)
    {
        info = "Builds a list from its arguments";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value ListFunction::Execute(int arity, Value* arg)
{
    auto* v = new CList(arity);

    for (int i = 0; i < arity; i++)
        (*v)[i] = arg[i];

    return Value(v);
}
//=============================================================================


class ListCountFunction : public Function
{
public:
    ListCountFunction(const char* n) :
        Function(n, 1, tlist) {}
    virtual Value Execute(int arity, Value* arg);
};

Value ListCountFunction::Execute(int, Value* arg)
{
    CList* v;
    arg[0].GetValue(v);
    return Value(v->Count());
}

//=============================================================================


class ListElemFunction : public Function
{
public:
    ListElemFunction(const char* n) :
        Function(n, 2, tlist, tnumber) {}
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);

private:
    eListIndexType indexType_{LINDEX_NUMBERS};
};

int ListElemFunction::ValidArguments(int arity, Value* arg)
{
    if (arity < 2 || arity > 4)
        return false;
    if (arg[0].GetType() != tlist)
        return false;


    // indexing can either be a set of numbers or a single vector

    if (arg[1].GetType() == tvector)  // vector
    {
        if (arity > 2)
            return false;
        else {
            indexType_ = LINDEX_VECTOR;
            return true;
        }
    }
    else  // number(s)
    {
        indexType_ = LINDEX_NUMBERS;

        for (int i = 1; i < arity; i++)
            if (arg[i].GetType() != tnumber)
                return false;
    }

    return true;
}

Value ListElemFunction::Execute(int arity, Value* arg)
{
    if (indexType_ == LINDEX_NUMBERS) {
        CList* v;
        int n;
        int m = 0;
        int s = 0;

        arg[0].GetValue(v);
        arg[1].GetValue(n);
        if (arity > 2)
            arg[2].GetValue(m);
        if (arity > 3)
            arg[3].GetValue(s);

        if (m < n)
            m = n;
        if (m > v->Count())
            m = v->Count();
        if (s <= 0)
            s = 1;

        if (n < 1 || n > v->Count())
            return Error("Index out of range. Index is %d, list size is %d",
                         n, v->Count());

        if (m == n)
            return (*v)[n - 1];


        int c = (m - n) / s + 1;
        auto* l = new CList(c);
        int i, j;

        for (i = 0, j = n - 1; i < c; i++, j += s)
            (*l)[i] = (*v)[j];

        return Value(l);
    }

    else  // vector indexing (i.e. the index is itself a vector of indexes)
    {
        CList* v;
        CVector* vi;
        arg[0].GetValue(v);
        arg[1].GetValue(vi);

        // the result vector
        auto* vnew = new CList(vi->Count());
        for (int i = 0; i < vi->Count(); i++) {
            int index = vi->getIndexedValue(i);
            if (index < 1 || index > v->Count())
                return Error("index %d(%d) is out of range. List size is %d", i + 1, index, v->Count());

            (*vnew)[i] = (*v)[index - 1];  // -1 to convert from 1-index (Macro) to 0-index (C)
        }

        return Value(vnew);
    }
}

//=============================================================================

class ListAddFunction : public Function
{
public:
    ListAddFunction(const char* n) :
        Function(n, 2, tlist, tlist) {}
    virtual Value Execute(int arity, Value* arg);
};


Value ListAddFunction::Execute(int, Value* arg)
{
    CList* v1;
    CList* v2;

    arg[0].GetValue(v1);
    arg[1].GetValue(v2);


    auto* v = new CList(v1->Count() + v2->Count());

    int i, n = 0;

    for (i = 0; i < v1->Count(); i++)
        (*v)[n++] = (*v1)[i];

    for (i = 0; i < v2->Count(); i++)
        (*v)[n++] = (*v2)[i];

    return Value(v);
}

//=============================================================================

class ListInFunction : public Function
{
public:
    ListInFunction(const char* n) :
        Function(n, 2, tany, tlist)
    {
        info = "Test if a value is in a list";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value ListInFunction::Execute(int, Value* arg)
{
    CList* v;
    vtype t = arg[0].GetType();
    char* eq = strcache("=");
    int x = 0;

    arg[1].GetValue(v);
    OpTest test;

    for (int i = 0; i < v->Count(); i++)
        if ((*v)[i].GetType() == t) {
            Owner()->Push((*v)[i]);
            Owner()->Push(arg[0]);
            Owner()->CallFunction(eq, 2);
            Value y = Owner()->Pop();
            if (test.Pass(y)) {
                x = 1;
                break;
            }
        }

    strfree(eq);
    return Value(x);
}

//=============================================================================

class ListFindFunction : public Function
{
public:
    ListFindFunction(const char* n) :
        Function(n, 2, tany, tlist)
    {
        info = "Find where an item occurs in a list";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


int ListFindFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2 && arity != 3)
        return false;
    if (arg[0].GetType() != tlist)
        return false;  // first arg must be a list
                       // second arg can be anything
    if ((arity == 3) && arg[2].GetType() != tstring)
        return false;  // third (optional) arg must be a string
    return true;
}


Value ListFindFunction::Execute(int arity, Value* arg)
{
    CList* v;
    vtype t = arg[1].GetType();
    char* eq = strcache("=");
    int x = 0;
    bool findAll = false;

    if (arity == 3) {
        const char* s;
        arg[2].GetValue(s);
        if (s && !strcmp(s, "all"))
            findAll = true;
        else
            marslog(LOG_WARN, "find(list, any, string) only accepts 'all' as its last argument. Supplied: '%s'", s);
    }

    arg[0].GetValue(v);


    std::vector<int> results;  // only need a vector if we're looking for more than 1 result

    int baseIndex = Context::BaseIndex();
    for (int i = 0; i < v->Count(); i++) {
        if ((*v)[i].GetType() == t) {
            Owner()->Push((*v)[i]);
            Owner()->Push(arg[1]);
            Owner()->CallFunction(eq, 2);
            Value y = Owner()->Pop();
            y.GetValue(x);
            if (x)  // did it match?
            {
                if (findAll) {
                    results.push_back(i + baseIndex);  // we want 'all', so add to our list
                }
                else {
                    strfree(eq);
                    return Value(i + baseIndex);  // we want just the first, to return it
                }
            }
        }
    }


    strfree(eq);

    if (findAll) {
        if (results.size() > 0) {
            auto* outlist = new CList(results.size());

            for (unsigned int i = 0; i < results.size(); i++) {
                (*outlist)[i] = results[i];
            }
            return Value(outlist);
        }
        else {
            return Value();  // looked for all the values, but did not find any - return nil
        }
    }
    else  // looked for the first value, but did not find it - return nil
    {
        return Value();
    }
}

//=============================================================================
class ListSortFunction : public Function
{
    int ReturnType;

    static CList* SortList;
    static const char* SortFunc;
    static Context* SortCtxt;

    static int Sort(const void* x, const void* y);

public:
    ListSortFunction(const char* n, int t) :
        Function(n),
        ReturnType(t)
    {
        info = "Sorts a list or indices (sort/sort_indices/sort_and_indices)";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


CList* ListSortFunction::SortList = 0;
const char* ListSortFunction::SortFunc = 0;
Context* ListSortFunction::SortCtxt = 0;

int ListSortFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 1 && arity != 2)
        return false;
    if (arg[0].GetType() != tlist)
        return false;
    if (arity == 2 && arg[1].GetType() != tstring)
        return false;
    return true;
}

int ListSortFunction::Sort(const void* x, const void* y)
{
    int* a = (int*)x;
    int* b = (int*)y;

    Value& v1 = (*SortList)[*b];
    Value& v2 = (*SortList)[*a];

    SortCtxt->Push(v1);
    SortCtxt->Push(v2);
    SortCtxt->CallFunction(SortFunc, 2);
    Value v = SortCtxt->Pop();

    int z;
    v.GetValue(z);
    return z ? 1 : -1;
}

Value ListSortFunction::Execute(int arity, Value* arg)
{
    static char* lt = 0;
    if (lt == 0)
        lt = strcache("<");

    SortCtxt = Owner();
    arg[0].GetValue(SortList);

    if (arity == 1)
        SortFunc = lt;
    else
        arg[1].GetValue(SortFunc);


    // int *x = (int*)NEW_ARRAY(int,SortList->Count()); // new int seems to crash !!!
    int* x = new int[SortList->Count()];

    int i;
    for (i = 0; i < SortList->Count(); i++)
        x[i] = i;

    qsort(x, SortList->Count(), sizeof(int), Sort);

    auto* c = new CList(SortList->Count());

    int baseIndex = Context::BaseIndex();
    switch (ReturnType) {
        case 1:  //-- 'sorted list'
            for (i = 0; i < SortList->Count(); i++)
                (*c)[i] = (*SortList)[x[i]];
            break;

        case 2:  //-- 'sorted indices'
            for (i = 0; i < SortList->Count(); i++)
                (*c)[i] = Value(x[i] + baseIndex);  //- indices from 1: 1,2,3,...
            break;

        case 3:  //-- 'sorted list and indices'
            for (i = 0; i < SortList->Count(); i++) {
                auto* pair = new CList(2);  //- memory leak here!?
                (*pair)[0] = (*SortList)[x[i]];
                (*pair)[1] = x[i] + baseIndex;  //- indices from 1: 1,2,3,...

                (*c)[i] = Value(pair);
            }
            break;
    }

    delete[] x;

    return Value(c);
}


//=============================================================================

class ListUniqueFunction : public Function
{
public:
    ListUniqueFunction(const char* n) :
        Function(n, 1, tlist)
    {
        info = "Returns a list with all the unique items in the input list";
    }
    virtual Value Execute(int arity, Value* arg);
};


Value ListUniqueFunction::Execute(int /*arity*/, Value* arg)
{
    CList* v;
    char* in = strcache("in");
    int x = 0;


    arg[0].GetValue(v);

    auto* list = new CList(0);  // start with an empty list and add one element at a time

    Value list_as_value(list);

    for (int i = 0; i < v->Count(); i++) {
        Owner()->Push((*v)[i]);        // call "element in u"
        Owner()->Push(list_as_value);  // call "element in u"
        Owner()->CallFunction(in, 2);  // call "element in u"
        Value y = Owner()->Pop();      // call "element in u"
        y.GetValue(x);

        if (!x)  // was it not in u?
        {
            list->Add((*v)[i]);
        }
    }

    strfree(in);

    return list_as_value;
}


/*******************************************************************************
 *
 * Function      : ListListBinOp
 *
 * Description   : Generic function for performing an arithmetic operation
 *                 between two lists; operation is performed for each pair
 *                 of elements. An error is returned if the lists are of
 *                 different sizes.
 *
 ******************************************************************************/

class ListListBinOp : public Function
{
    binproc F_;

public:
    ListListBinOp(const char* n, binproc f) :
        Function(n, 2, tlist, tlist) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
};

Value ListListBinOp::Execute(int, Value* arg)
{
    CList* v;
    CList* w;

    arg[0].GetValue(v);
    arg[1].GetValue(w);

    char* funcname = strcache(Name());

    if (v->Count() != w->Count()) {
        // marslog(LOG_WARN, "lists have different numbers of elements (%d and %d) - '%s' returning nil", v->Count(), w->Count(), funcname);
        return Value();
    }

    auto* p = new CList(v->Count());

    int count = v->Count();
    for (int i = 0; i < count; i++) {
        Owner()->Push((*v)[i]);
        Owner()->Push((*w)[i]);
        Owner()->CallFunction(funcname, 2);
        Value y = Owner()->Pop();
        (*p)[i] = y;
    }

    return Value(p);
}


/*******************************************************************************
 *
 * Function      : NumListBinOp
 *
 * Description   : Generic function for performing an arithmetic operation
 *                 between a list and a number (or a date)
 *
 ******************************************************************************/

class NumListBinOp : public Function
{
    binproc F_;

public:
    NumListBinOp(const char* n, binproc f) :
        Function(n) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NumListBinOp::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[1].GetType() == tlist && (arg[0].GetType() == tnumber || arg[0].GetType() == tdate))
        return true;
    if (arg[0].GetType() == tlist && (arg[1].GetType() == tnumber || arg[1].GetType() == tdate))
        return true;
    return false;
}


Value NumListBinOp::Execute(int, Value* arg)
{
    char* funcname = strcache(Name());
    bool listFirst = arg[0].GetType() == tlist;  // is the list the first argument?
    bool isDate;                                 // is the other argument a date?
    double n;
    Date d;
    CList* v;


    if (!listFirst) {
        isDate = arg[0].GetType() == tdate;
        if (isDate)
            arg[0].GetValue(d);
        else
            arg[0].GetValue(n);
        arg[1].GetValue(v);

        auto* p = new CList(v->Count());

        // for each point, compute

        int count = v->Count();
        for (int i = 0; i < count; i++) {
            if (isDate)
                Owner()->Push(d);
            else
                Owner()->Push(n);
            Owner()->Push((*v)[i]);
            Owner()->CallFunction(funcname, 2);
            Value y = Owner()->Pop();
            (*p)[i] = y;
        }

        return Value(p);
    }
    else {
        isDate = arg[1].GetType() == tdate;
        arg[0].GetValue(v);
        if (isDate)
            arg[1].GetValue(d);
        else
            arg[1].GetValue(n);

        auto* p = new CList(v->Count());


        // for each point, compute

        int count = v->Count();
        for (int i = 0; i < count; i++) {
            Owner()->Push((*v)[i]);
            if (isDate)
                Owner()->Push(d);
            else
                Owner()->Push(n);
            Owner()->CallFunction(funcname, 2);
            Value y = Owner()->Pop();
            (*p)[i] = y;
        }

        return Value(p);
    }
}


/*******************************************************************************
 *
 * Function      : ListUniOp
 *
 * Description   : Generic function for performing an arithmetic operation
 *                 on a list
 *
 ******************************************************************************/

class ListUniOp : public Function
{
    uniproc F_;

public:
    ListUniOp(const char* n, uniproc f) :
        Function(n, 1, tlist) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
};

Value ListUniOp::Execute(int, Value* arg)
{
    CList* v;  // original  list
    CList* p;  // resultant list
    char* funcname = strcache(Name());

    arg[0].GetValue(v);

    p = new CList(v->Count());

    int count = v->Count();
    for (int i = 0; i < count; i++) {
        Owner()->Push((*v)[i]);
        Owner()->CallFunction(funcname, 1);
        Value y = Owner()->Pop();
        (*p)[i] = y;
    }

    return Value(p);
}


static void install(Context* c)
{
    int i;

    c->AddFunction(new ListFunction("list"));
    c->AddFunction(new ListCountFunction("count"));
    c->AddFunction(new ListElemFunction("[]"));
    c->AddFunction(new ListAddFunction("&"));
    c->AddFunction(new ListInFunction("in"));
    c->AddFunction(new ListFindFunction("find"));
    c->AddFunction(new ListSortFunction("sort", 1));
    c->AddFunction(new ListSortFunction("sort_indices", 2));
    c->AddFunction(new ListSortFunction("sort_and_indices", 3));
    c->AddFunction(new ListUniqueFunction("unique"));


    // binary operations

    for (i = 0; BinOps[i].symb; i++)
        c->AddFunction(new ListListBinOp(BinOps[i].symb, BinOps[i].proc));

    for (i = 0; BinOps[i].symb; i++)
        c->AddFunction(new NumListBinOp(BinOps[i].symb, BinOps[i].proc));


    // Mult op as Binary op
    // (the above comment is from bufr.cc, but it's not exactly clear how these
    // operations differ from the BinOps ones)

    for (i = 0; MulOps[i].symb; i++)
        c->AddFunction(new ListListBinOp(MulOps[i].symb, MulOps[i].proc));

    for (i = 0; MulOps[i].symb; i++)
        c->AddFunction(new NumListBinOp(MulOps[i].symb, MulOps[i].proc));


    // Unary operations

    for (i = 0; UniOps[i].symb; i++)
        c->AddFunction(new ListUniOp(UniOps[i].symb, UniOps[i].proc));
}

static Linkage linkage(install);
