/***************************** 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 <ctype.h>
#include <string.h>

#include <string>

#include "macro.h"

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

int CString::Write(FILE* f)
{
    fprintf(f, "%s", string);
    return ferror(f);
}

void CString::ToRequest(request*& x)
{
    static request* r = nullptr;
    if (r == nullptr)
        r = empty_request("STRING");
    set_value(r, "VALUE", "%s", string);
    x = r;
}

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

void CString::Dump1()
{
    if (!string)
        return;

    char* p = string;
    while (*p) {
        if (isprint(*p))
            std::cout << *p;
        else
            std::cout << '<' << (int)*p << '>';
        p++;
    }
}

void CString::Dump2()
{
    std::cout << "'";
    Dump1();
    std::cout << "'";
}


Value StringFunction::Execute(int, Value* arg)
{
    const char* p;
    arg[0].GetValue(p);
    return Value(p);
}

class ParseFunction : public Function
{
public:
    ParseFunction(const char* n) :
        Function(n)
    {
        info = "Splits a string into tokens.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};
int ParseFunction::ValidArguments(int arity, Value* arg)
{
    /* Note that it is not clear why this function accepts non-strings
       as a second parameter - when would this be useful?
       It can accept numbers and dates as strings to be split
       - you can probably do most of the date-splitting functionality
       with other macro functions, but there could be a use for
       splitting a number on a dot, for instance.

       ir/2007-08-15.
    */

    if (arity != 1 && arity != 2 && arity != 3)
        return false;

    if (arity == 3)  // type parameter specified?
    {
        if (arg[2].GetType() != tstring)  // yes, but it has to be a string
            return false;

        arity--;  // 3rd param ok, but no need to check this parameter again
    }


    for (int i = 0; i < arity; i++)
        switch (arg[i].GetType()) {
            case tnumber:
            case tstring:
            case tdate:
                break;

            default:
                return false;
        }
    return true;
}


Value ParseFunction::Execute(int arity, Value* arg)
{
    const char* p;
    const char* q = " \t";
    const char* type;
    bool asString = false;
    arg[0].GetValue(p);

    if (arity > 1)
        arg[1].GetValue(q);


    if (arity > 2)  // did the user specify that the results should all be
    {               // of a particular type?
        arg[2].GetValue(type);
        if (!strcmp(type, "string"))
            asString = true;
        else
            return Error("the parse() function currently only accepts 'string' as its third argument. It was given '%s'", type);
    }

    CList* l = nullptr;

    if (*q == '\0')  //-- empty delimiter => split into single chars
    {
        char chr[2];
        chr[1] = '\0';

        int cnt = strlen(p);
        l = new CList(cnt);

        for (int i = 0; i < cnt; ++i) {
            chr[0] = p[i];
            (*l)[i] = Value(chr);
        }
    }
    else {
        char* s = new char[strlen(p) + 1];
        char* t = s;
        strcpy(s, p);

        int cnt = 0;
        while (strtok(t, q)) {
            cnt++;
            t = nullptr;
        }

        t = s;
        strcpy(s, p);
        int i = 0;
        char* a;
        l = new CList(cnt);

        while ((a = strtok(t, q))) {
            if (!asString && isdate(a))
                (*l)[i] = Value(Date(a));
            else if (!asString && is_number(a))
                (*l)[i] = Value(atof(a));
            else
                (*l)[i] = Value(a);
            t = nullptr;
            i++;
        }

        delete[] s;
    }

    return Value(l);
}


class SubstringFunction : public Function
{
public:
    SubstringFunction(const char* n) :
        Function(n)
    {
        info = "Returns a substring.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int SubstringFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 3)
        return false;

    if (arg[0].GetType() != tstring)
        return false;

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

    return true;
}
Value SubstringFunction::Execute(int /*arity*/, Value* arg)
{
    const char* s;
    arg[0].GetValue(s);  //-- original string

    int c1, c2;
    arg[1].GetValue(c1);  //-- first char index (1,..,n)
    arg[2].GetValue(c2);  //-- last char index

    Value v;  //-- return value

    if (c1 > (signed)strlen(s) || c1 < 1 || c2 < c1) {
        v = "\0";  //-- no such substring!!!
    }
    else {
        int sslen = c2 - c1 + 1;  //-- substring length
        char* ss = new char[sslen + 1];

        strncpy(ss, (s + c1 - 1), sslen);
        ss[sslen] = '\0';  //-- make sure ss is terminated

        v = ss;
        delete[] ss;
    }

    return v;
}


class StringSearchFunction : public Function
{
public:
    StringSearchFunction(const char* n) :
        Function(n, 2, tstring, tstring)
    {
        info = "Search for a given substring.";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value StringSearchFunction::Execute(int /*arity*/, Value* arg)
{
    const int cNotFound = -1;

    const char* os;
    arg[0].GetValue(os);  //-- get original string

    const char* ss;
    arg[1].GetValue(ss);  //-- get search string

    if (!ss || !os)
        return Value(cNotFound);  //-- cannot search with NULL ptrs

    int oslen = strlen(os);
    int sslen = strlen(ss);

    if (oslen == 0 || sslen == 0 || sslen > oslen)
        return Value(cNotFound);  //-- empty string or search string too long

    const char* ssloc = strstr(os, ss);  //-- look for search string

    if (!ssloc)
        return Value(cNotFound);  //-- string not found

    int loca = ssloc - os + 1;  //-- string found - calculate location of 1st char

    return Value(loca - (1 - Context::BaseIndex()));
}


class StringAddFunction : public Function
{
public:
    StringAddFunction(const char* n) :
        Function(n, 2, tstring | tnumber | tdate,
                 tstring | tnumber | tdate) {}
    virtual Value Execute(int arity, Value* arg);
};

Value StringAddFunction::Execute(int, Value* arg)
{
    const char* c;

    arg[0].GetValue(c);
    std::string s = c;

    arg[1].GetValue(c);
    std::string s2 = c;

    s = s + s2;

    Value v(s.c_str());
    return v;
}


class LengthFunction : public Function
{
public:
    LengthFunction(const char* n) :
        Function(n, 1, tstring)
    {
        info = "Returns the length of a string.";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value LengthFunction::Execute(int, Value* arg)
{
    const char* p = nullptr;
    arg[0].GetValue(p);
    return Value(p ? strlen(p) : 0);
}


class AsciiFunction : public Function
{
public:
    AsciiFunction(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Returns the corresponding ASCII character";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value AsciiFunction::Execute(int, Value* arg)
{
    static char cbuf[2] = {'?', '\0'};

    int p = 0;
    arg[0].GetValue(p);
    cbuf[0] = (char)p;
    return Value(cbuf);
}


class StringCaseFunction : public Function
{
    boolean upper;

public:
    StringCaseFunction(const char* n, boolean up) :
        Function(n, 1, tstring),
        upper(up)
    {
        info = "Sets to unique case of letters";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value StringCaseFunction::Execute(int, Value* arg)
{
    const char* s;
    arg[0].GetValue(s);  //-- original string
    Value v;             //-- return value

    int len = strlen(s);
    if (len > 0) {
        char* ss = strdup(s);

        for (int c = 0; c < len; ++c) {
            if (upper)
                ss[c] = (char)toupper((int)ss[c]);
            else
                ss[c] = (char)tolower((int)ss[c]);
        }

        v = ss;

        free(ss);
    }
    else
        v = "\0";

    return v;
}

class NumberFunction : public Function
{
public:
    NumberFunction(const char* n) :
        Function(n, 1, tstring)
    {
        info = "Returns the number represented by the string";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value NumberFunction::Execute(int, Value* arg)
{
    const char* p = nullptr;
    arg[0].GetValue(p);
    double d = atof(p);
    return Value(d);
}


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

static int s_gt(const char* a, const char* b)
{
    return strcmp(a, b) > 0;
}
static int s_lt(const char* a, const char* b)
{
    return strcmp(a, b) < 0;
}
static int s_eq(const char* a, const char* b)
{
    return strcmp(a, b) == 0;
}
static int s_ne(const char* a, const char* b)
{
    return strcmp(a, b) != 0;
}
static int s_ge(const char* a, const char* b)
{
    return strcmp(a, b) >= 0;
}
static int s_le(const char* a, const char* b)
{
    return strcmp(a, b) <= 0;
}

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

class StringCmpFunction : public Function
{
    using cproc = int (*)(const char*, const char*);
    cproc F_;

public:
    StringCmpFunction(const char* n, cproc f) :
        Function(n, 2, tstring, tstring),
        F_(f)
    {
    }

    virtual Value Execute(int arity, Value* arg);
};

Value StringCmpFunction::Execute(int, Value* arg)
{
    const char* p;
    const char* q;

    arg[0].GetValue(p);
    arg[1].GetValue(q);

    return Value(F_(p, q));
}

static void install(Context* c)
{
    c->AddGlobal(new Variable("newline", Value("\n")));
    c->AddGlobal(new Variable("tab", Value("\t")));

    c->AddFunction(new StringFunction("string"));
    c->AddFunction(new StringAddFunction("&"));

    c->AddFunction(new StringCmpFunction(">", s_gt));
    c->AddFunction(new StringCmpFunction("<", s_lt));
    c->AddFunction(new StringCmpFunction(">=", s_ge));
    c->AddFunction(new StringCmpFunction("<=", s_le));
    c->AddFunction(new StringCmpFunction("=", s_eq));
    c->AddFunction(new StringCmpFunction("<>", s_ne));

    c->AddFunction(new StringCaseFunction("lowercase", false));
    c->AddFunction(new StringCaseFunction("uppercase", true));

    c->AddFunction(new AsciiFunction("ascii"));
    c->AddFunction(new ParseFunction("parse"));
    c->AddFunction(new LengthFunction("length"));
    c->AddFunction(new SubstringFunction("substring"));
    c->AddFunction(new StringSearchFunction("search"));
    c->AddFunction(new NumberFunction("number"));
}

static Linkage Link(install);
