#include "state.h"

#include "reference.h"
#include "scalar.h"

#include <iostream>

using Frost::s_str; using Frost::s_uint; using Frost::s_double;
using Frost::s_int; using Frost::s_bool; using Frost::s_refptr;
using Frost::s_map; using Frost::s_ctnr; using Frost::s_char;
using Frost::s_wptr; using Frost::nullptr;

std::ostream& operator << (std::ostream& out, const s_str& s)
{
    return out << s.Get();
}

State::State()
{
    // Mandatory registrations
    RegisterOperatorList({{"[]", 5}, {"!", 4}, {"^", 3}, {"*", 2}, {"/", 2}, {"+", 1}, {"-", 1}});

    Symbol::Register(*this);
    Reference::Register(*this);
    Scalar::Register(*this);

    SetLastResult(nullptr);
}

s_bool State::CutExpression(s_ctnr<s_str>& exp, const s_str& sv, CutFunction_t mCutIf, s_str& sError, s_bool bKeepCut) const
{
    s_uint uiLastPos;
    s_str::const_iterator iter;
    s_int iParenthesis, iVector, iBracket;
    foreach (iter, sv)
    {
        if (iParenthesis != 0)
        {
            if (*iter == '(')      ++iParenthesis;
            else if (*iter == ')') --iParenthesis;
        }
        else if (iVector != 0)
        {
            if (*iter == '<')      ++iVector;
            else if (*iter == '>') --iVector;
        }
        else if (iBracket != 0)
        {
            if (*iter == '[')      ++iBracket;
            else if (*iter == ']') --iBracket;
        }
        else
        {
            if (*iter == '(') ++iParenthesis;
            else if (*iter == ')')
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Too many closing ')'";
                return false;
            }
            else if (*iter == '<') ++iVector;
            else if (*iter == '>')
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Too many closing '>'";
                return false;
            }
            else if (*iter == '[') ++iBracket;
            else if (*iter == ']')
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Too many closing ']'";
                return false;
            }
            else
            {
                s_str o;
                if (mCutIf(iter, sv, o))
                {
                    s_str sub = sv.ExtractRange(uiLastPos, iter-sv.Begin());
                    if (!sub.IsEmpty())
                        exp.PushBack(sub);
                    if (!o.IsEmpty() && bKeepCut)
                        exp.PushBack(o);
                    iter += o.GetSize().Get()-1;
                    uiLastPos = (iter-sv.Begin()) + 1;
                }
            }
        }
    }

    if (iParenthesis != 0u)
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Unterminated '('";
        return false;
    }

    if (iVector != 0u)
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Unterminated '<'";
        return false;
    }

    if (iBracket != 0u)
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Unterminated '['";
        return false;
    }

    s_str sub = sv.ExtractRange(uiLastPos, s_uint::INF);
    if (!sub.IsEmpty())
        exp.PushBack(sub);

    return true;
}

s_refptr<Symbol> State::ParseSymbol(const s_str& n, s_str sv, s_str& sError, s_bool bAllowUndefined)
{
    sv.Trim(' ');

    if (sv.IsEmpty())
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Empty symbol";
        return nullptr;
    }

    s_ctnr<s_str> exp;
    s_bool (State::*pFunc)(s_str::const_iterator, const s_str&, s_str&) const;
    pFunc = &State::IsOperator;
    if (!CutExpression(exp, sv, std::bind(pFunc, this, std::_Placeholder<1>(), std::_Placeholder<2>(), std::_Placeholder<3>()), sError))
        return nullptr;

    if (exp.GetSize() == 1)
    {
        s_str s = exp[0];

        if (s.StartsWith("(") && s.EndsWith(")"))
        {
            s.EraseFromStart(1); s.EraseFromEnd(1);
            return ParseSymbol(n, s, sError);
        }

        s_ctnr<s_str> lMatches;
        s_map<s_str, ConstructorMatch_t>::const_iterator iterMatch;
        foreach (iterMatch, lConstructorMatchArray)
        {
            if (iterMatch->second(s))
                lMatches.PushBack(iterMatch->first);
        }

        if (lMatches.GetSize() > 1)
        {
            if (!sError.IsEmpty()) sError.PushBack('\n');
            sError += "\tError : Ambiguous constructor : '"+s+"'.\n"
                      "\t        Possible candidates : ";

            s_ctnr<s_str>::iterator iter;
            foreach (iter, lMatches)
                sError += *iter + ", ";

            sError.EraseFromEnd(2);
            return nullptr;
        }
        else if (lMatches.GetSize() == 1)
        {
            return lConstructorArray[lMatches[0]](*this, n, s, sError);
        }

        s_uint uiBSPos = s.FindPos("[");
        s_uint uiBEPos = s.FindPos("]");
        if (uiBSPos.IsValid() && uiBEPos.IsValid() && uiBSPos < uiBEPos)
        {
            if (uiBEPos - uiBSPos == 1)
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Illegal use of '[.]' with empty argument";
                return nullptr;
            }

            s_str a = s.ExtractRange(0, uiBSPos);
            if (a.IsEmpty())
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Illegal use of '[.]' on empty object";
                return nullptr;
            }

            s_refptr<Symbol> sa = ParseSymbol("", a, sError);
            if (!sa) return nullptr;

            s_str b = s.ExtractRange(uiBSPos+1, uiBEPos);
            s_refptr<Symbol> sb = ParseSymbol("", b, sError);
            if (!sb) return nullptr;

            s_refptr<Symbol> r = BinaryOperator("[]", sa, sb, sError);
            if (!r) return nullptr;

            r->name = n;
            return r;
        }

        if (g.Find(s))
        {
            s_refptr<Symbol> r = g[s]->Clone();
            r->name = n;
            if (r->GetType() == "Symbol" && !n.IsEmpty())
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tWarning : 'Symbol=Symbol' does nothing";
            }

            return r;
        }

        s_str sSymbolError;
        if (CheckName(s, sSymbolError))
        {
            if (n.IsEmpty() && bAllowUndefined)
                return s_refptr<Symbol>(new Symbol(s));
            else
            {
                if (!sError.IsEmpty()) sError.PushBack('\n');
                sError += "\tError : Unknown symbol '" + s + "'";
                return nullptr;
            }
        }
    }

    s_ctnr<s_str>::iterator iterOP;
    s_uint uiLowestWeight = s_uint::INF;

    s_ctnr<s_str>::iterator iterExp;
    foreach (iterExp, exp)
    {
        if (IsOperator(*iterExp))
        {
            s_uint uiWeight = GetOperatorWeight(*iterExp);
            if (uiWeight <= uiLowestWeight)
            {
                iterOP = iterExp;
                uiLowestWeight = uiWeight;
            }
        }
    }

    if (uiLowestWeight == s_uint::INF)
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Unable to parse expression '" + sv + "', no operator found";
        return nullptr;
    }

    s_str sLeft;
    iterExp = exp.Begin();
    while (iterExp != iterOP)
    {
        sLeft += *iterExp;
        ++iterExp;
    }

    ++iterExp;

    s_str sRight;
    while (iterExp != exp.End())
    {
        sRight += *iterExp;
        ++iterExp;
    }

    if (!sLeft.IsEmpty() && !sRight.IsEmpty())
    {
        s_refptr<Symbol> a = ParseSymbol("", sLeft, sError);
        if (!a) return nullptr;

        s_refptr<Symbol> b = ParseSymbol("", sRight, sError);
        if (!b) return nullptr;

        s_refptr<Symbol> r = BinaryOperator(*iterOP, a, b, sError);
        if (!r) return nullptr;

        r->name = n;
        return r;
    }
    else if (!sLeft.IsEmpty())
    {
        s_refptr<Symbol> a = ParseSymbol("", sLeft, sError);
        if (!a) return nullptr;

        s_refptr<Symbol> r = LeftUnaryOperator(*iterOP, a, sError);
        if (!r) return nullptr;

        r->name = n;
        return r;
    }
    else if (!sRight.IsEmpty())
    {
        s_refptr<Symbol> a = ParseSymbol("", sRight, sError);
        if (!a) return nullptr;

        s_refptr<Symbol> r = RightUnaryOperator(*iterOP, a, sError);
        if (!r) return nullptr;

        r->name = n;
        return r;
    }
    else
    {
        if (!sError.IsEmpty()) sError.PushBack('\n');
        sError += "\tError : Operator '" + *iterOP + "' is missing its operands";
        return nullptr;
    }
}

void State::ParseLine(const s_str& sLine)
{
    s_ctnr<s_str> lInstructions = sLine.Cut(";");
    s_ctnr<s_str>::iterator iterI;
    foreach (iterI, lInstructions)
    {
        s_str& s = *iterI;

        if (s.IsEmpty())
            continue;

        // Comment
        if (s.StartsWith("//"))
            continue;

        s_uint uiEqPos = s.FindPos("=");
        s_uint uiDecPos = s.FindPos(":");
        s_uint uiAmpPos = s.FindPos("&");
        if (uiEqPos.IsValid() && uiDecPos.IsValid() && uiDecPos == uiEqPos-1)
        {
            // Definition
            // TODO ?
        }
        else if (uiEqPos.IsValid() && uiAmpPos.IsValid() && uiAmpPos == uiEqPos-1)
        {
            // Reference creation
            s_str n = s.ExtractRange(0, uiAmpPos);
            n.Trim(' ');

            s_str sError;
            if (!CheckName(n, sError))
            {
                std::cout << sError << std::endl;
                continue;
            }

            s_str sv = s.ExtractRange(uiEqPos+1, s_uint::INF);
            sv.Trim(' ');

            s_refptr<Symbol> r(new Reference(n, this, sv));
            s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(n);
            if (iter != g.End())
                iter->second = r;
            else
                iter = g.Insert(MakePair(n, r)).First();

            std::cout << "\tDefining " << iter->second->GetTrueType() << " : " << iter->second->PrintContent() << std::endl;

            SetLastResult(r);
        }
        else if (uiEqPos.IsValid())
        {
            // Assignation
            s_str n = s.ExtractRange(0, uiEqPos);
            n.Trim(' ');

            s_str sError;
            if (!CheckName(n, sError))
            {
                std::cout << sError << std::endl;
                continue;
            }

            s_str sv = s.ExtractRange(uiEqPos+1, s_uint::INF);

            s_refptr<Symbol> r = ParseSymbol(n, sv, sError);
            if (!sError.IsEmpty())
                std::cout << sError << std::endl;

            if (!r) continue;

            s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(n);
            if (iter != g.End())
                iter->second = r;
            else
                iter = g.Insert(MakePair(n, r)).First();

            std::cout << "\tDefining " << iter->second->GetTrueType() << " : " << iter->second->PrintContent() << std::endl;

            SetLastResult(r);
        }
        else if (uiDecPos.IsValid())
        {
            // Declaration
            s_str n = s.ExtractRange(0, uiDecPos);
            n.Trim(' ');

            s_str sError;
            if (!CheckName(n, sError))
            {
                std::cout << sError << std::endl;
                continue;
            }

            s_str t = s.ExtractRange(uiDecPos+1, s_uint::INF);
            t.Trim(' ');

            if (!TypeExists(t))
            {
                std::cout << "\tError : Unknown type '" << t << "'" << std::endl;
                continue;
            }

            s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(n);
            if (iter != g.End())
                iter->second = CreateEmptySymbol(t, n);
            else
                iter = g.Insert(MakePair(n, CreateEmptySymbol(t, n))).First();

            std::cout << "\tDefining " << t << " : " << iter->second->PrintContent() << std::endl;

            SetLastResult(iter->second);
        }
        else if (s.StartsWith("~"))
        {
            // Remove a symbol
            s.EraseFromStart(1);
            if (s.IsEmpty())
            {
                std::cout << "\tWarning : This command will erase all symbols.\n"
                          << "\t          Are you sure ? ([y|Enter]/n) ";
                s_str sTemp;
                getline(std::cin, sTemp.Get());
                if (sTemp.IsEmpty() || sTemp == "y" || sTemp == "Y")
                {
                    g.Clear();
                    std::cout << "\tAll symbols erased" << std::endl;
                }
            }
            else
            {
                s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(s);
                if (iter != g.End())
                {
                    s_str sType = iter->second->GetType();
                    g.Erase(iter);
                    std::cout << "\t" << sType << " '" << s << "' erased" << std::endl;
                }
                else
                {
                    std::cout << "\tError : Unknown symbol '" << s << "'" << std::endl;
                    continue;
                }
            }

            SetLastResult(nullptr);
        }
        else if (s.StartsWith("?"))
        {
            // Print information about a symbol/function
            // or get some help
            s.EraseFromStart(1);
            if (s.IsEmpty())
            {
                ShowHelp();
            }
            else
            {
                s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(s);
                if (iter != g.End())
                    std::cout << "\t" << iter->second->GetType() << " : " << iter->second->PrintContent() << std::endl;
                else
                    std::cout << "\t'" << s << "' is undefined" << std::endl;
            }

            SetLastResult(nullptr);
        }
        else
        {
            // Implicit declaration or printing
            s_str sError;
            s_refptr<Symbol> r = ParseSymbol("", s, sError, true);
            if (!r)
            {
                std::cout << sError << std::endl;
                continue;
            }

            if (r->name.IsEmpty())
            {
                std::cout << "\t" << r->GetType() << " : " << r->PrintContent() << std::endl;
                SetLastResult(r);
            }
            else
            {
                s_map<s_str, s_refptr<Symbol>>::iterator iter = g.Get(r->name);
                if (iter != g.End())
                {
                    std::cout << "\t" << iter->second->GetType() << " : " << iter->second->PrintContent() << std::endl;
                    SetLastResult(r);
                }
                else
                {
                    s_str sError;
                    if (!CheckName(r->name, sError))
                    {
                        std::cout << sError << std::endl;
                        continue;
                    }

                    iter = g.Insert(MakePair(r->name, r)).First();
                    std::cout << "\tDefining " << iter->second->GetType() << " : " << iter->second->PrintContent() << std::endl;
                    SetLastResult(r);
                }
            }
        }
    }
}

s_refptr<Symbol> State::CreateEmptySymbol(const s_str& sType, const s_str& sName) const
{
    s_map<s_str, std::function<s_refptr<Symbol>(const s_str&)>>::const_iterator iter = lTypeArray.Get(sType);
    if (iter != lTypeArray.End())
        return (iter->second)(sName);
    else
        return nullptr;
}

s_bool State::TypeExists(const s_str& t) const
{
    return lTypeArray.Find(t);
}

s_bool State::CheckName(const s_str& n, s_str& e) const
{
    if (n == "_")
    {
        if (!e.IsEmpty()) e.PushBack('\n');
        e += "\tError : Reference to last result '_' is not assignable";
        return false;
    }

    s_str::const_iterator si;
    foreach (si, n)
    {
        if (!s_str::IsAlphaNumeric(*si))
        {
            if (!e.IsEmpty()) e.PushBack('\n');
            e += "\tError : Only alphanumeric names are allowed for symbols, in '" + n + "'";
            return false;
        }
    }

    return true;
}

s_bool State::IsExpression(const s_str& s) const
{
    s_map<s_str, s_uint>::const_iterator iter;
    foreach (iter, lOperatorList)
    {
        if (s.Find(iter->first))
            return true;
    }

    return false;
}

s_bool State::IsOperator(s_str::const_iterator iter, const s_str& s, s_str& o) const
{
    s_map<s_str, s_uint>::const_iterator iter2;
    foreach (iter2, lOperatorList)
    {
        s_str::const_iterator iter3 = iter;
        s_str::const_iterator iter4 = iter2->first.Begin();

        s_bool bMatch = true;
        while (iter3 != s.End() && iter4 != iter2->first.End())
        {
            if (*iter3 != *iter4)
            {
                bMatch = false;
                break;
            }

            ++iter3;
            ++iter4;
        }

        if (bMatch)
        {
            o = iter2->first;
            return true;
        }
    }

    return false;
}

s_bool State::IsOperator(const s_str& s) const
{
    return lOperatorList.Find(s);
}

s_uint State::GetOperatorWeight(const s_str& s) const
{
    s_map<s_str,s_uint>::const_iterator iter = lOperatorList.Get(s);
    if (iter != lOperatorList.End())
        return iter->second;
    else
        return s_uint::NaN;
}

s_refptr<Symbol> State::BinaryOperator(const s_str& o, s_refptr<Symbol> a, s_refptr<Symbol> b, s_str& e) const
{
    s_map<s_str, s_map<s_str, s_map<s_str, BinaryOperator_t>>>::const_iterator iter1 = lBinaryOperatorArray.Get(o);
    if (iter1 != lBinaryOperatorArray.End())
    {
        s_refptr<Symbol> dra = Dereference<Symbol>(a);

        const s_map<s_str, s_map<s_str, BinaryOperator_t>>& mOP1 = iter1->second;
        s_map<s_str, s_map<s_str, BinaryOperator_t>>::const_iterator iter2 = mOP1.Get(dra->GetType());
        if (iter2 != mOP1.End())
        {
            s_refptr<Symbol> drb = Dereference<Symbol>(b);

            const s_map<s_str, BinaryOperator_t>& mOP2 = iter2->second;
            s_map<s_str, BinaryOperator_t>::const_iterator iter3 = mOP2.Get(drb->GetType());
            if (iter3 != mOP2.End())
                return iter3->second(dra, drb, e);
        }
    }

    s_str an;
    if (a->name.IsEmpty())
        an = a->PrintContent();
    s_str bn;
    if (b->name.IsEmpty())
        bn = b->PrintContent();

    if (!e.IsEmpty()) e.PushBack('\n');
    e += "\tError : No match for operator '" + a->GetType() + " " + o + " " + b->GetType() + "' in :\n" +
         "\t        \t" + an + " " + o + " " + bn;

    return nullptr;
}

s_refptr<Symbol> State::LeftUnaryOperator(const s_str& o, s_refptr<Symbol> a, s_str& e) const
{
    s_map<s_str, s_map<s_str, UnaryOperator_t>>::const_iterator iter1 = lLeftUnaryOperatorArray.Get(o);
    if (iter1 != lLeftUnaryOperatorArray.End())
    {
        s_refptr<Symbol> dra = Dereference<Symbol>(a);

        const s_map<s_str, UnaryOperator_t>& mOP1 = iter1->second;
        s_map<s_str, UnaryOperator_t>::const_iterator iter2 = mOP1.Get(dra->GetType());
        if (iter2 != mOP1.End())
            return iter2->second(dra, e);
    }

    s_str an;
    if (a->name.IsEmpty())
        an = a->PrintContent();

    if (!e.IsEmpty()) e.PushBack('\n');
    e += "\tError : No match for operator '" + a->GetType() + " " + o + "' in :\n" +
         "\t        \t" + an + " " + o;

    return nullptr;
}

s_refptr<Symbol> State::RightUnaryOperator(const s_str& o, s_refptr<Symbol> a, s_str& e) const
{
    s_map<s_str, s_map<s_str, UnaryOperator_t>>::const_iterator iter1 = lRightUnaryOperatorArray.Get(o);
    if (iter1 != lRightUnaryOperatorArray.End())
    {
        s_refptr<Symbol> dra = Dereference<Symbol>(a);

        const s_map<s_str, UnaryOperator_t>& mOP1 = iter1->second;
        s_map<s_str, UnaryOperator_t>::const_iterator iter2 = mOP1.Get(dra->GetType());
        if (iter2 != mOP1.End())
            return iter2->second(dra, e);
    }

    s_str an;
    if (a->name.IsEmpty())
        an = a->PrintContent();

    if (!e.IsEmpty()) e.PushBack('\n');
    e += "\tError : No match for operator '" + o + " " + a->GetType() + "' in :\n" +
         "\t        \t" + o + " " + an;

    return nullptr;
}

void State::RegisterOperatorList(const s_map<s_str, s_uint>& lList)
{
    lOperatorList = lList;
}

void State::SetLastResult(s_refptr<Symbol> s)
{
    if (s)
        g["_"] = s;
    else
        g["_"] = s_refptr<Symbol>(new Reference("", this, ""));
}

void State::ShowHelp() const
{
    std::string t = "   ";
    std::string t2 = t+t;

    std::cout << "\tHelp :\n"
        << "------------------------------\n\n"
        << t  << "Available syntaxes :\n"
        << t2 << "Variable:Type : (re-)defines a symbol of name 'Variable'\n"
        << t2 << "                and of type 'Type', not initialized.\n"
        << t2 << "Variable=Expression : (re-)defines a symbol of name 'Variable'\n"
        << t2 << "                      and assigns it the value of 'Expression'.\n"
        << t2 << "                      The type of this symbol is deduced from the\n"
        << t2 << "                      type of 'Expression'.\n"
        << t2 << "Expression : prints the result of 'Expression'\n"
        << t2 << "?Variable : prints the content of the symbol 'Variable'.\n"
        << t2 << "~Variable : deletes the symbol 'Variable'.\n"
        << t2 << "~ : deletes all symbols (starts anew).\n"
        << t2 << "_ : recalls the last result.\n"
        << t2 << "\n"
        << t  << "Expressions :\n"
        << t2 << "An expression is a set of operators acting on symbols.\n"
        << t2 << "All expressions are space invariant, that is you can place any\n"
        << t2 << "number of spaces ' ' between operators and symbols without harm.\n"
        << t2 << "Note of course that, for two symbols 'a' and 'b' :\n"
        << t2 << "                      ab != a b\n"
        << t2 << "The former declares a new symbol 'ab', while the latter is illegal.\n"
        << t2 << "\n"
        << t  << "Operators :\n"
        << t2 << "Most operators are binary, that is they take two arguments :\n"
        << t2 << "            arg1 operator arg2\n"
        << t2 << "but you also have left-unary operators :\n"
        << t2 << "            arg1 operator\n"
        << t2 << "and right-unary operators :\n"
        << t2 << "            operator arg1\n"
        << t2 << "Operators have a defined priority, like in mathematics where\n"
        << t2 << "multiplication has priority over addition or substraction.\n"
        << t2 << "If you want to force priority of a certain operator, you can\n"
        << t2 << "surround it and its arguments with parenthesis '(' and ')'.\n"
        << t2 << "\n"
        << t  << "Built-in types :\n"
        << t2 << "Symbol : Abstract type, no value, no operators.\n"
        << t2 << "Scalar : Real number (double precision).\n"
        << t2 << "         Available operators : *, /, + and -.\n"
        << t2 << "Vector : 1-dimensional container of arbitrary (but fixed) length.\n"
        << t2 << "         Available operators : + and - (plus ^ for 3-vectors).\n"
        << t2 << "         You can create a vector using :\n"
        << t2 << "                     <elt1, elt2, elt3, ... >\n"
        << t2 << "         where 'eltX' are elements of the vector.\n"
        << t2 << "         Vectors are homogeneous, that is they can only contain\n"
        << t2 << "         one type of data at the same time.\n"
        << t2 << "         You can access individual elements using :\n"
        << t2 << "                          vector[index]\n"
        << t2 << "         or equivalently :\n"
        << t2 << "                          vector[]index\n"
        << t2 << "         where 'vector' is your vector, and 'index' a scalar\n"
        << t2 << "         value or expression.\n"
        << t2 << "Reference : String value containing an expression 'exp'. Whenever\n"
        << t2 << "            one uses a reference in an expression, the expression\n"
        << t2 << "            'exp' is evaluated and its result is used in place of\n"
        << t2 << "            the reference. You can create a reference on any\n"
        << t2 << "            expression using :\n"
        << t2 << "                            &expression\n"
        << t2 << "            and the following is available for assignment :\n"
        << t2 << "                         ref &= expression\n";
}
