/* 
 * The MIT License
 *
 * Copyright 2020 The OpenNARS authors.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "Decision.h"

double DECISION_THRESHOLD = DECISION_THRESHOLD_INITIAL;
double ANTICIPATION_THRESHOLD = ANTICIPATION_THRESHOLD_INITIAL;
double ANTICIPATION_CONFIDENCE = ANTICIPATION_CONFIDENCE_INITIAL;
double MOTOR_BABBLING_CHANCE = MOTOR_BABBLING_CHANCE_INITIAL;
//Inject action event after execution or babbling
void Decision_Execute(Decision *decision)
{
    assert(decision->operationID > 0, "Operation 0 is reserved for no action");
    decision->op = operations[decision->operationID-1];
    //and add operator feedback
    if(decision->arguments.atoms[0] > 0) //operation with args
    {
        Term operation = {0};
        operation.atoms[0] = Narsese_AtomicTermIndex(":"); //<args --> ^op>
        if(!Term_OverrideSubterm(&operation, 1, &decision->arguments) || !Term_OverrideSubterm(&operation, 2, &decision->op.term))
        {
            return;
        }
        NAR_AddInputBelief(operation);
    }
    else //atomic operation / operator
    {
        NAR_AddInputBelief(decision->op.term);
    }
    (*decision->op.action)(decision->arguments);
}

//"reflexes" to try different operations, especially important in the beginning
static Decision Decision_MotorBabbling()
{
    Decision decision = (Decision) {0};
    int n_ops = 0;
    for(int i=0; i<OPERATIONS_MAX && operations[i].action != 0; i++)
    {
        n_ops = i+1;
    }
    if(n_ops > 0)
    {
        decision.operationID = 1+(myrand() % (n_ops));
        IN_DEBUG (
            printf(" NAR BABBLE %d\n", decision.operationID);
        )
        decision.execute = true;
    }
    return decision;
}

static Decision Decision_ConsiderImplication(long currentTime, Event *goal, int considered_opi, Implication *imp)
{
    Decision decision = (Decision) {0};
    IN_DEBUG
    (
        printf("CONSIDERED IMPLICATION: impTruth=(%f, %f)", imp->truth.frequency, imp->truth.confidence);
        Narsese_PrintTerm(&imp->term);
        puts("");
    )
    //now look at how much the precondition is fulfilled
    Concept *prec = imp->sourceConcept;
    Event *precondition = &prec->belief_spike; //a. :|:
    if(precondition != NULL)
    {
        Event ContextualOperation = Inference_GoalDeduction(goal, imp); //(&/,a,op())! :\:
        double operationGoalTruthExpectation = Truth_Expectation(Inference_OperationDeduction(&ContextualOperation, precondition, currentTime).truth); //op()! :|:
        IN_DEBUG
        (
            printf("CONSIDERED PRECON: desire=%f ", operationGoalTruthExpectation);
            Narsese_PrintTerm(&prec->term);
            fputs("\nCONSIDERED PRECON truth ", stdout);
            Truth_Print(&precondition->truth);
            fputs("CONSIDERED goal truth ", stdout);
            Truth_Print(&goal->truth);
            fputs("CONSIDERED imp truth ", stdout);
            Truth_Print(&imp->truth);
            printf("CONSIDERED time %ld\n", precondition->occurrenceTime);
            Narsese_PrintTerm(&precondition->term); puts("");
        )
        //<(precon &/ <args --> ^op>) =/> postcon>. -> [$ , postcon precon : _ _ _ _ args ^op
        Term operation = Term_ExtractSubterm(&imp->term, 4); //^op or [: args ^op]
        if(!Narsese_isOperator(operation.atoms[0])) //it is an operation with args, not just an atomic operator, so remember the args
        {
            assert(Narsese_isOperator(operation.atoms[2]), "If it's not atomic, it needs to be an operation with args here");
            decision.arguments = Term_ExtractSubterm(&imp->term, 9); 
        }
        decision.operationID = considered_opi;
        decision.desire = operationGoalTruthExpectation;
    }
    return decision;
}

int stampID = -1;
Decision Decision_BestCandidate(Concept *goalconcept, Event *goal, long currentTime)
{
    Decision decision = (Decision) {0};
    Implication bestImp = {0};
    long bestComplexity = COMPOUND_TERM_SIZE_MAX+1;
    Concept *cbest_predicate = NULL;
    Decision decisionGeneral = (Decision) {0};
    Implication bestImpGeneral = {0};
    long bestComplexityGeneral = COMPOUND_TERM_SIZE_MAX+1;
    Concept *cbest_predicateGeneral = NULL;
    Substitution subs = Variable_Unify(&goalconcept->term, &goal->term);
    if(subs.success)
    {
        for(int opi=1; opi<=OPERATIONS_MAX && operations[opi-1].action != 0; opi++)
        {
            for(int j=0; j<goalconcept->precondition_beliefs[opi].itemsAmount; j++)
            {
                if(!Memory_ImplicationValid(&goalconcept->precondition_beliefs[opi].array[j]))
                {
                    Table_Remove(&goalconcept->precondition_beliefs[opi], j--);
                    continue;
                }
                Implication imp = goalconcept->precondition_beliefs[opi].array[j];
                bool impHasVariable = Variable_hasVariable(&imp.term, true, true, true);
                bool success;
                imp.term = Variable_ApplySubstitute(imp.term, subs, &success);
                if(success)
                {
                    assert(Narsese_copulaEquals(imp.term.atoms[0], '$'), "This should be an implication!");
                    Term left_side_with_op = Term_ExtractSubterm(&imp.term, 1);
                    Term left_side = Narsese_GetPreconditionWithoutOp(&left_side_with_op); //might be something like <#1 --> a>
                    for(int cmatch_k=0; cmatch_k<concepts.itemsAmount; cmatch_k++)
                    {
                        Concept *cmatch = concepts.items[cmatch_k].address;
                        if(!Variable_hasVariable(&cmatch->term, true, true, true))
                        {
                            Substitution subs2 = Variable_Unify(&left_side, &cmatch->term);
                            if(subs2.success)
                            {
                                Implication specific_imp = imp; //can only be completely specific
                                bool success;
                                specific_imp.term = Variable_ApplySubstitute(specific_imp.term, subs2, &success);
                                if(success && !Variable_hasVariable(&specific_imp.term, true, true, true))
                                {
                                    specific_imp.sourceConcept = cmatch;
                                    specific_imp.sourceConceptId = cmatch->id;
                                    Decision considered = Decision_ConsiderImplication(currentTime, goal, opi, &specific_imp);
                                    int specific_imp_complexity = Term_Complexity(&specific_imp.term);
                                    if(impHasVariable)
                                    {
                                        if(considered.desire > decisionGeneral.desire || (considered.desire == decisionGeneral.desire && specific_imp_complexity < bestComplexityGeneral))
                                        {
                                            decisionGeneral = considered;
                                            cbest_predicateGeneral = cmatch;
                                            bestComplexityGeneral = specific_imp_complexity;
                                            bestImpGeneral = imp;
                                        }
                                    }
                                    else
                                    {
                                        if(considered.desire > decision.desire || (considered.desire == decision.desire && specific_imp_complexity < bestComplexity))
                                        {
                                            decision = considered;
                                            decision.specialized = true;
                                            cbest_predicate = cmatch;
                                            bestComplexity = specific_imp_complexity;
                                            bestImp = imp;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    //use general solution only if the specific solution doesn't exceed the threshold
    if(decisionGeneral.desire > decision.desire && decision.desire < DECISION_THRESHOLD)
    {
        decision = decisionGeneral;
        cbest_predicate = cbest_predicateGeneral;
        bestImp = bestImpGeneral;
    }
    if(decision.desire < DECISION_THRESHOLD)
    {
        return (Decision) {0};
    }
    //increase usefulness
    assert(cbest_predicate != NULL, "Above decision threshold but postcondition concept is NULL!");
    cbest_predicate->usage = Usage_use(cbest_predicate->usage, currentTime, false);
    Term subject_with_op = Term_ExtractSubterm(&bestImp.term, 1);
    Term subject = Narsese_GetPreconditionWithoutOp(&subject_with_op);
    Concept *cbest_subject = Memory_Conceptualize(&subject, currentTime);
    if(cbest_subject != NULL)
    {
        cbest_subject->usage = Usage_use(cbest_subject->usage, currentTime, false);
    }
    //set execute and return execution
    printf("decision expectation %f impTruth=(%f, %f): future=%ld ", decision.desire, bestImp.truth.frequency, bestImp.truth.confidence, bestImp.occurrenceTimeOffset);
    Narsese_PrintTerm(&bestImp.term); puts("");
    decision.execute = true;
    return decision;
}

void Decision_AssumptionOfFailure(int operationID, long currentTime)
{
    assert(operationID >= 0 && operationID <= OPERATIONS_MAX, "Wrong operation id, did you inject an event manually?");
    for(int j=0; j<concepts.itemsAmount; j++)
    {
        Concept *postc = concepts.items[j].address;
        for(int  h=0; h<postc->precondition_beliefs[operationID].itemsAmount; h++)
        {
            if(!Memory_ImplicationValid(&postc->precondition_beliefs[operationID].array[h]))
            {
                Table_Remove(&postc->precondition_beliefs[operationID], h);
                h--;
                continue;
            }
            Implication imp = postc->precondition_beliefs[operationID].array[h]; //(&/,a,op) =/> b.
            Concept *current_prec = imp.sourceConcept;
            Event *precondition = &current_prec->belief_spike; //a. :|:
            if(precondition != NULL && precondition->type != EVENT_TYPE_DELETED)
            {
                assert(precondition->occurrenceTime != OCCURRENCE_ETERNAL, "Precondition should not be eternal!");
                Event updated_precondition = Inference_EventUpdate(precondition, currentTime);
                Event op = { .type = EVENT_TYPE_BELIEF,
                             .truth = (Truth) { .frequency = 1.0, .confidence = 0.9 },
                             .occurrenceTime = currentTime };
                bool success;
                Event seqop = Inference_BeliefIntersection(&updated_precondition, &op, &success); //(&/,a,op). :|:
                if(success)
                {
                    Event result = Inference_BeliefDeduction(&seqop, &imp); //b. :/:
                    if(Truth_Expectation(result.truth) > ANTICIPATION_THRESHOLD)
                    {
                        Implication negative_confirmation = imp;
                        Truth TNew = { .frequency = 0.0, .confidence = ANTICIPATION_CONFIDENCE };
                        Truth TPast = Truth_Projection(precondition->truth, 0, imp.occurrenceTimeOffset);
                        negative_confirmation.truth = Truth_Eternalize(Truth_Induction(TNew, TPast));
                        negative_confirmation.stamp = (Stamp) { .evidentalBase = { -stampID } };
                        assert(negative_confirmation.truth.confidence >= 0.0 && negative_confirmation.truth.confidence <= 1.0, "(666) confidence out of bounds");
                        Implication *added = Table_AddAndRevise(&postc->precondition_beliefs[operationID], &negative_confirmation);
                        if(added != NULL)
                        {
                            added->sourceConcept = negative_confirmation.sourceConcept;
                            added->sourceConceptId = negative_confirmation.sourceConceptId;
                        }                                
                        stampID--;
                    }
                }
            }
        }
    }
}

Decision Decision_Suggest(Concept *postc, Event *goal, long currentTime)
{
    Decision babble_decision = {0};
    //try motor babbling with a certain chance
    if(myrand() < (int)(MOTOR_BABBLING_CHANCE * MY_RAND_MAX))
    {
        babble_decision = Decision_MotorBabbling();
    }
    //try matching op if didn't motor babble
    Decision decision_suggested = Decision_BestCandidate(postc, goal, currentTime);
    if(!babble_decision.execute || decision_suggested.desire > MOTOR_BABBLING_SUPPRESSION_THRESHOLD)
    {
       return decision_suggested;
    }
    return babble_decision;
}
