/**
© Florian Joncour, 2013-2018 florian@zetta-sys.com

Ce logiciel est un programme informatique faisant interface à la bibliothèque
Perspective3D, un outil de modélisation 3D à partir de vues orthographiques 2D.

Ce logiciel est régi par la licence CeCILL-C soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL-C telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme,  le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard  l'attention de l'utilisateur est attirée sur les risques
associés au chargement,  à l'utilisation,  à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant  des  connaissances  informatiques approfondies.  Les
utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL-C, et que vous en avez accepté les
termes.
**/

#ifndef EVALUATEUR_H
#define EVALUATEUR_H

/**
  Evaluateur.
  Le code est du point de vue de la synthaxe proche des langages fonctionnels.

  On peut assigner des variables comme avec une expression du type:
  "var=42"

  Les manipuler:
  var=((var*3)+2)

  Les opérations doivent être mises entre parenthèses pour fixer l'odre d'évaluation, mais plusieurs opérations peuvent se suivre
    sans parenthèse, dans ce cas, ce sont les prioritées opératoires qui définissent l'ordre:

  var = (8 + ( 50 +2*-5)*3)

  L'ordre d'évaluation est le suivant:
  Priorité haute: * (multiplication, / (division), % (modulo), ^ (puissance)
  Priorité moyenne : + (addition), - (soustraction)
  Priorité basse : Tous les opérateurs booléens. ==, !=, <, >, <=, >=, &, |

  L'ensemble des opérateurs doivent être suivis et précédés d'un nombre, d'une constante, d'une variable ou d'un résultat de calcul mis entre parenthèses.

  En plus des opérateurs, il est possible d'appeler des fonctions, comme par exemple (sin x)

  Les fonctions peuvent avoir un nombre variable d'arguments, et sont appelées avec une priorité opératoire haute.

  Le langage dispose de ses opérateurs, constantes et fonctions prédéfinis mais peut être étendu (une seule extension possible pour un interpréteur donné)
  en héritant de la classe Evaluateur::DictionnaireLangage et en réimplémentant l'ensemble des fonctions virtuelles sur le modèle de la classe LangageInterne.

  Après calcul, il est possible de récupérer les valeurs des variables au moyens des fonctions prévues dans l'espace publique de l'interpréteur.

**/

#include "API/perspective_sys.h"

#include <string>
#include <unordered_map>
#include <list>
#include <vector>

/* Pour définir la précision double -> string */
#include <iomanip>
#include <sstream>

#ifdef DEBUG
//#ifndef AFFICHE_EXEC_EVAL
//    #define AFFICHE_EXEC_EVAL
//#endif // DEBUG

//#ifndef DEBUG_EVAL
//    #define DEBUG_EVAL
//#endif // DEBUG_EVAL
#endif // DEBUG

namespace Evaluateur DLL_LOCAL
{
    typedef long eval_int;
    typedef double eval_float;
    typedef unsigned char eval_char;

    enum TYPE_VARIABLES_EVAL { TYPE_NUL=0, TYPE_REF, TYPE_INT, TYPE_FLOAT, TYPE_BOOL };

#ifdef DEREF_VAR_EVAL
    #undef DEREF_VAR_EVAL
#endif // DEREF_VAR_EVAL
#define DEREF_VAR_EVAL(X) ((X.type_var == Evaluateur::TYPE_FLOAT) ? (X.var.f) : ((X.type_var == Evaluateur::TYPE_INT) ? (X.var.i) : (X.var.b)))

    union DLL_LOCAL eval_types_t /* Types de variables gérées */
    {
            eval_types_t() : i(0) { }
            eval_types_t(eval_int i_) : i(i_) { }
            eval_types_t(eval_float f_) : f(f_) { }
            eval_types_t(bool b_) : b(b_) { }
            eval_int i;
            eval_float f;
            bool b;
    };

    struct DLL_LOCAL val_tok /* Définition d'une variable (avec le type et la valeur). */
    {
        explicit val_tok(pint t_var, eval_types_t v, bool valide_=false) : type_var(t_var), var(v), valide(valide_) { }
        explicit val_tok() : type_var(TYPE_NUL), var(false), valide(false) { }

        inline std::string encode(bool controle_valide=false) const
        {
            if (valide || !controle_valide)
            {
                std::ostringstream out;

                if (type_var == TYPE_REF)
                {
                    out << var.i;
                    return out.str();
                }
                if (type_var == TYPE_INT)
                {
                    out << var.i;
                    return out.str();
                }
                if (type_var == TYPE_FLOAT)
                {
                    out << std::setprecision(12) << var.f;
                    return out.str();
                }
                if (type_var == TYPE_BOOL)
                {
                    return (var.b) ? std::string("1") : std::string("0");
                }
            }
            return std::string("nil");
        }

        inline std::string encodeType() const
        {
            if (type_var == TYPE_REF)
            {
                return "($)";
            }
            if (type_var == TYPE_INT)
            {
                return "(int)";
            }
            if (type_var == TYPE_FLOAT)
            {
                return "(float)";
            }
            if (type_var == TYPE_BOOL)
            {
                return "(bool)";
            }
            else
            {
                return "(nil)";
            }
        }

        inline bool Nul() const
        {
            if (type_var == TYPE_INT)
            {
                return (var.i == 0);
            }
            if (type_var == TYPE_FLOAT)
            {
                return ( ((var.f < 0.f) ? -var.f : var.f) < 0.0001f );
            }
            if (type_var == TYPE_BOOL)
            {
                return var.b == false;
            }
            return true;
        }

//        pint taille_tableau;
        pint type_var;
        eval_types_t var;
        bool valide;
    };

    struct DLL_LOCAL variable_tok
    {
            explicit variable_tok() : nom(std::string()), valeur() { }
            explicit variable_tok(const std::string &n, pint t_var, eval_types_t v) : nom(n), valeur(t_var, v) { }

            inline std::string encode() const
            {
                return nom + " " + valeur.encodeType() + " " + valeur.encode();
            }


            inline bool Nul() const
            {
                return (nom.empty());
            }

            std::string nom;
            val_tok valeur;
    };

    enum TYPE_FONCTIONS_EVAL { TYPE_FCT_NUL=0, OPERATEUR_0, OPERATEUR_1, OPERATEUR_2, FONCTION };
    enum DOMAINE_FONCTIONS_EVAL { DOM_NUL=0, DOM_DEPILE, DOM_ASSIGNATION, DOM_CONSTANTE, DOM_OPERATEUR, DOM_OPERATEUR_BOOL, DOM_FONCTION_INTERNE, DOM_FONCTION_EXTERNE };

    /* Concaténation de deux nombres entiers, le second aura 24 bits réservés, donc le premier en aura 8 si il s'agit d'un nombre sur 32 bits. */
    #define CONCAT_INT_FCT(DOMAINE, ID) ((DOMAINE << 24) + ID)
    #define DOMAINE_FCT(CONCAT) (CONCAT >> 24)
    #define DOMAINE_FCT_OP(OPERATION) (DOMAINE_FCT(OPERATION.id_fonction))
    #define ID_FCT(CONCAT) (CONCAT & 0xffffff)

    #define ID_FCT_ASSIGNATION (CONCAT_INT_FCT(DOM_ASSIGNATION, 0))
    #define ID_FCT_DEPILE (CONCAT_INT_FCT(DOM_DEPILE, 0))

    struct DLL_LOCAL operation_tok
    {
        public:
            explicit operation_tok(puint fct) :
                resultat(), id_fonction(fct) { }

            explicit operation_tok(puint fct, const val_tok &res) :
                resultat(res), id_fonction(fct) { }

            explicit operation_tok() : resultat(), id_fonction(0) { }

            val_tok resultat; /* Resultat après opération */
            puint id_fonction; /* Type d'opérateur ou fonction, concaténation avec la macro CONCAT_INT_FCT() */
    };

    typedef std::vector<std::string> string_list_t;
    typedef std::vector<operation_tok> operations_list_t;
    typedef std::list<operations_list_t> operations_arbre_t;

    inline DLL_LOCAL bool CompareStrEval(const std::string &s1, const std::string &s2)
    {
        return (s1.compare(s2) == 0);
    }

//    typedef std::queue<operation_tok> operations_list_t;

    /* Liste des fonctions supportées et association: */
    struct DLL_LOCAL def_fonction /* Définition d'une fonction. */
    {
            const char *nom_fonction; /* Nom de la fonction utilisée dans le code source */
            puint type_operation; /* Type d'opération (voir l'enum TYPE_FONCTIONS_EVAL). */
            pint nb_parametres; /* Nombre de paramètres, -1 si variable, 2 dans le cas des opérateurs, après c'est du cas par cas suivant la fonction. */
            puint id_fonction; /* Identifiant de fonction (on peut y mettre ce qu'on veut à condition de donner un id unique).*/
    };

    static const def_fonction FonctionNulle = { "nil", Evaluateur::TYPE_FCT_NUL, 0, 0 };

    class DLL_LOCAL DictionnaireLangage
    {
        public:
            inline explicit DictionnaireLangage() { ; }
            virtual ~DictionnaireLangage() { ; }

            virtual bool Exec(puint id_fonction, Evaluateur::operation_tok *args=0, puint n_args=0) const = 0;
            /* Exécution d'une fonction d'après son id */

            virtual pint IdFonction(const std::string &s) const = 0;
            /* Renvoi l'id de fonction pour le nom donné en argument dans le domaine par défaut. */

            virtual pint IdFonctionDom(const std::string &s, puint domaine) const = 0;
            /* Renvoi l'id de fonction pour le domaine donné en argument ou TYPE_FCT_NUL si la fonction n'est pas définie. */

            virtual const def_fonction &ReferenceFonction(puint id) const = 0;
            /* Renvoi la référence d'une fonction suivant son id. */

            inline pint TypeFonction(puint id) const
            {
                const def_fonction &fct = ReferenceFonction(id);
                {
                    return fct.type_operation;
                }
            }

            inline pint NombreParametresFonction(puint id) const
            /* Renvoi le nombre de paramètres nécessaires à la fonction donnée en argument, renvoi -1 si le nombre de paramètres est dynamique. */
            {
                const def_fonction &fct = ReferenceFonction(id);
                if (fct.type_operation == Evaluateur::TYPE_FCT_NUL)
                {
                    return 0;
                }
                return fct.nb_parametres;
            }

            inline const char *NomFonction(puint id) const
            {
                const def_fonction &fct = ReferenceFonction(id);
                if (fct.type_operation == Evaluateur::TYPE_FCT_NUL)
                {
                    return 0;
                }
                return fct.nom_fonction;
            }
    };

    class DLL_LOCAL EvaluateurExp
    {
        typedef std::unordered_map<std::string, variable_tok> type_tas;

        public:
            explicit EvaluateurExp(DictionnaireLangage *ext=0);

            ~EvaluateurExp();

            void Reinit(); /* Réinitialise l'évaluateur. Toutes les variables seront supprimées. */
            bool AjoutVariable(const std::string &code); /* Ajoute une variable de type "i=42" ou "f=0.42" */

            bool AjoutVariableBool(const std::string &variable, bool valeur); /* Ajoute une variable de type bool */
            bool AjoutVariableInt(const std::string &variable, eval_int valeur); /* Ajoute une variable de type int */
            bool AjoutVariableFloat(const std::string &variable, eval_float valeur); /* Ajoute une variable de type float */

            bool Exec(const std::string &code); /* Exécute un segment de code (peut contenir plusieurs lignes) */

            inline const val_tok &EtatMachine() const /* Renvoi la dernière valeur manipulée ou calculée. */
            { return etat_machine; }

            inline bool EtatMachineBool() const
            /* Renvoi true si la dernière donnée manipulée est valide, qu'elle soit de type booléenne ou entière et différente de 0. */
            {
                return ((etat_machine.valide) && (etat_machine.type_var == TYPE_BOOL || etat_machine.type_var == TYPE_INT) && (DEREF_VAR_EVAL(etat_machine) != 0));
            }

            inline bool ExecCond(const std::string &code, bool var_sortie=false)
            /* Exécute une condition (le code doit contenir une opération booléenne.
                Renvoi true si tout s'est bien passé et que la condition est remplie, sinon la valeur de var_sortie.
                var_sortie désigne la valeur de sortie en cas d'erreur pour éviter une éventuelle boucle infinie en cas d'erreur dans le code
                si la condition est exécutée récursivement. */
            {
                if (Exec(code))
                {
                    return EtatMachineBool();
                }
                return var_sortie;
            }

            bool ValeurFloat(const std::string &var, bool evaluation_tableau, eval_float &n);
            /* Assigne dans 'n' la valeur de la variable donnée en argument sous forme décimale et renvoi true si la conversion est possible, sinon false. */

            bool ValeurInt(const std::string &var, bool evaluation_tableau, pint &n);
            /* Assigne dans 'n' la valeur de la variable donnée en argument sous forme entière et renvoi true si la conversion est possible, sinon false. */

            bool ValeurShort(const std::string &var, bool evaluation_tableau, pshort &n);
            /* Assigne dans 'n' la valeur de la variable donnée en argument sous forme entière et renvoi true si la conversion est possible, sinon false. */

            bool ValeurBool(const std::string &var, bool evaluation_tableau, bool &n);
            /* Assigne dans 'n' la valeur de la variable donnée en argument sous forme booléenne et renvoi true si la conversion est possible, sinon false. */

            bool VariableExiste(const std::string &var);
            const variable_tok &VariableConst(const std::string &var, bool evaluation_tableau=false, bool recherche_constantes=false); /* Renvoi la valeur de la variable de tout type stockée. */

        private:

            std::string StrOperation(const operation_tok &op) const;

#ifdef DEBUG
            void AfficheOperations(const std::string &txt, const operations_list_t &operations) const;
            void AfficheOperations(const std::string &txt, const operations_arbre_t &operations) const;
            void AffichePile() const;
            void AfficheTas() const;
#endif // DEBUG

            void ReinitPile();
//            std::string RenommeVariableTableau(const std::string &var) const;
            variable_tok &Variable(const std::string &var, bool evaluation_tableau=false, bool recherche_constantes=false);

            std::string TraiteNomVariable(const std::string &nom);

            bool AssigneVal(variable_tok &var, const std::string &code);
            bool AssigneVal(const std::string &var, const std::string &code, bool ignore_affectation_tableaux=false);
            bool AssigneTableau(const std::string &var, const std::string &code);
            bool PermuteVals(const std::string &var1, const std::string &var2);
            void MessageErreur(const std::string &s);
            bool PreTraiteLigne(std::string &code);
            bool EvalLigne(std::string &code, bool reinit_pile=true);
            bool Evalcode(std::string &code, operations_arbre_t &arbre_operations);

            bool DepileVal(val_tok &v); /* Assigne dans v le dernier élément de la pile et retire le dernier élément de la pile. */
            bool EmpileVal(const val_tok &v, bool init=false); /* Ajoute v dans la pile. */

            puint EvalFonction(const std::string &s, puint type_op) const;
            val_tok EvalNombre(const std::string &s) const;

            pint PositionCharPrecedent(const std::string &code, pint pos) const;
            pint PositionCharSuivant(const std::string &code, pint pos) const;
            pint PositionSeparateurSuivant(const std::string &code, pint pos, bool passe_interieur=false) const;
            pint PositionOpAffectation(const std::string &code) const;
            pint PositionOpPermutation(const std::string &code) const;
            std::string MotSuivant(const std::string &code, puint position) const;

            puint TypeOperation(puint id_fonction) const;
            pint NombreOperandes(puint id_fonction) const;

            bool OperationTok(puint id_op, operations_list_t &operations);
            bool ExecOperations(operations_arbre_t &arbre_operations);
            bool ExecOperationsTok(operations_list_t &operations, pint niveau_priorite_operatoire);
            bool GenOperations(std::string &code, puint pos1, puint lg_tok, operations_list_t &operations);

            pint CompteurErreurs;

            val_tok etat_machine; /* Dernier état calculé */
            variable_tok var_def_0;
            variable_tok var_def_temp;
            std::vector<val_tok>Pile_Variables; /* Pile de variables (y sont stockés les retours de calcul) */
            type_tas Tas_Variables; /* Tas des variables utilisateur. */

            DictionnaireLangage *Lang_Interne;
            DictionnaireLangage *Lang_Extension;
    };
} /* namespace Evaluateur */

#endif /* EVALUATEUR_H */
