#include "../const.h"
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "Interaction.h"
#include "../Interaction/Init_per_para.h"

// For use on PC only!


// This functions reads from a configuration file
// AIBO personality and save it in an appropriate data structure
bool
InitPersonality(Personality& p, const Init_per_para& parameters)
{

int i, j, random;
state current, next;
input emotion;
float prob;
bool result;
char filename[MAX_LENGTH_FILE];
char per_files[PER_NUM][MAX_LENGTH_FILE] = PERFILES;
FILE* f;


#ifdef FUNC_TRACK
PRN(("Starting InitPersonality\n"));
#endif

// Allocating memory for Personality
p = Personality();
for (i = 0; i < STATE_NUM; i++) {
    for (j = 0; j < STATE_NUM; j++) {
          if (p.transition_prob[i][j] == NULL)
             return false;
     }
     if (p.transition_prob[i] == NULL)
             return false;
}
if (p.transition_prob == NULL)
   return false;


   
// Initializing random seed
srand((unsigned)time(NULL));


if (!parameters.per_random) {
    // Opening the selected Personality configuration file
    sprintf(filename, "%s%s", INIT_FOLDER, parameters.per_file);
}
else {
    // Choosing randomly a Personality configuration file
    random = (int) (PER_NUM * (rand() / (RAND_MAX + 1.0)));
    sprintf(filename, "%s%s", INIT_FOLDER, per_files[random]);
    PRN(("Selected Personality File: %s\n\n", per_files[random]));
}
f = fopen(filename, "r+");
if (f == 0) {
   PRN(("can't open %s\n", filename));
   return false;
}



/// Reading configuration file
fscanf(f, "#####PERSONALITY_CONFIGURATION::%*s\n");
fscanf(f, "#####\n");
while(fscanf(f, "#STATE::%*s %d\n", &current)) {
   while (fscanf(f, "P(%d, %d) = %f\n", &emotion, &next, &prob))
         p.transition_prob[current][emotion][next] = prob;
   fscanf(f, "#####\n");
}
fscanf(f, "#####END_OF_FILE#####\n");
fclose(f);


// Checking probabilities correctness
result =  CheckProb(p);

#ifdef FUNC_TRACK
PRN(("Ending InitPersonality\n"));
#endif

return result;

}

// This function determines AIBO's next emotional state
// on the basis of AIBO's loaded personality and the user's
// emotional state given as an input.
bool
Interact(Personality& p, Emotion& emotion, const Init_per_para& parameters)
{

int i;
double random, partial_sum;

#ifdef FUNC_TRACK
PRN(("Starting Interact\n"));
#endif

random = rand() / (RAND_MAX + 1.0);
partial_sum = 0.0;


// Selecting AIBO next emotional state
i = 0;
while (partial_sum <= random && i < STATE_NUM) {
   if (p.transition_prob[p.current_state][emotion.name][i])
      partial_sum += p.transition_prob[p.current_state][emotion.name][i];
   i++;
}
i--;
p.current_state = (state)i;

// Updating personality
if (parameters.aibo_evolving)
   UpdatePersonality(p, emotion, parameters);




// Outputting AIBO state
switch(p.current_state) {
     case AIBO_NEUTRAL:
                       PRN(("My current state is NEUTRAL\n"));
                       break;

     case AIBO_JOYFUL:
                       PRN(("My current state is JOYFUL\n"));
                       break;

     case AIBO_SAD:
                       PRN(("My current state is SAD\n"));
                       break;

     case AIBO_ANGRY:
                       PRN(("My current state is ANGRY\n"));
                       break;

     default:
                       break;

}

#ifdef FUNC_TRACK
PRN(("Ending Interact\n"));
#endif

return true;

}

// This function updates the transition probabilities according
// to the interaction history
void
UpdatePersonality(Personality& p, Emotion& emotion, const Init_per_para& parameters)
{


#ifdef FUNC_TRACK
PRN(("Starting UpdatePersonality\n"));
#endif


// Updating interaction points...
switch(emotion.name) {
       // USER_NEUTRAL
       case 0:
             break;
       // USER_JOYFUL and USER_SURPRISED: nice interaction update
       case 1:
             p.nice_interaction_points += parameters.joyful_points;
             break;
       case 3:
             p.nice_interaction_points += parameters.surprised_points;
             break;
       // USER_SAD and USER_FEARFUL: sad interaction update
       case 2:
             p.sad_interaction_points += parameters.sad_points;
             break;
       case 5:
             p.sad_interaction_points += parameters.fearful_points;
             break;
       // USER_ANGRY and USER_DISGUSTED: bad interaction update
       case 4:
             p.bad_interaction_points += parameters.angry_points;
             break;
       case 6:
             p.bad_interaction_points += parameters.disgusted_points;
             break;
       default:
             break;
}

// Updating personality...
if (parameters.aibo_evolving) {
   if(parameters.aibo_imitating)
       UpdatePersonalityImitating(p, parameters);
   else
       UpdatePersonalityCompensating(p, parameters);
}

#ifdef FUNC_TRACK
PRN(("Ending UpdatePersonality\n"));
#endif

return;

}

// Personality is updated adopting an imitative attitude
void
UpdatePersonalityImitating(Personality& p, const Init_per_para& parameters)
{

int i, j, s, k, end, num_update, num_subtract;
int to_update[STATE_NUM];                                       // states whose entry probability will be increased
bool subtract[STATE_NUM];                                       // states whose entry probability will be diminished
bool first[STATE_NUM];
float sum, add, total, delta, precision = 0.000001;             // 'precision' used for comparation of float values
bool updating = false;


#ifdef FUNC_TRACK
PRN(("Starting UpdatePersonalityImitating\n"));
#endif

// Many nice inputs occured, so we increase transition probabilities to AIBO_JOYFUL state
if ((p.nice_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.nice_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_JOYFUL;
   end = 1;
   printf("\nI am updating my personality: I will be more joyful.\n\n");
}
// Many sad inputs occured, so we increase transition probabilities to AIBO_SAD state
else if ((p.sad_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.sad_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_SAD;
   end = 1;
   printf("\nI am updating my personality: I will be sadder.\n\n");
}
// Many bad inputs occured, so we increase transition probabilities to AIBO_ANGRY state
else if ((p.bad_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.bad_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_ANGRY;
   end = 1;
   printf("\nI am updating my personality: I will be angrier.\n\n");
}


if (updating) {
   for (i = 0; i < STATE_NUM; i++)
       subtract[i] = true;
   for (k = 0; k < end; k++)
       subtract[to_update[k]] = false;

   for(i = 0; i < EM_NUM + 1; i++) {
         for(s = 0; s < STATE_NUM; s++) {
                     
                     num_update = end;                              // Number of states whose entry probability will be increased
                     num_subtract = STATE_NUM - num_update;         // Number of states whose entry probability will be decreased
                     
                     total = parameters.update_value * num_update;  // Total probability amount to shift to "to_update" states
                                                                        // Every entry probability for those states will be increased,
                                                                        // if possible, by 'parameters.update_value' amount.


                     sum = 0.0;                                     // total amount of probability we can subtract from not_to_update states
                     for (j = 0; j < STATE_NUM; j++) {
                           if (subtract[j]) {
                                 sum += p.transition_prob[s][i][j];
                           }
                     }
                     if ((sum + precision) < total)                // we don't have enough probability! We are going to use
                        total = sum;                               // only the amount that is available

                        
                     delta = total/num_subtract;                   // Amount of probability that will be subtracted from "not_to_update" states
                     add = total/num_update;                       // Amount of probability that will be added to "to_update" states



                     for (j = 0; j < STATE_NUM; j++)
                            first[j] = true;
                     for (j = 0; j < STATE_NUM; j++) {
                           // If state j has entry probability < delta, delta must be recomputed.
                           // Every state will satisfy the if condition at most once, so we use 'first'
                           // array to assure that no state will be considered more than once.
                           if (subtract[j] && first[j] && (p.transition_prob[s][i][j] + precision) < delta) {
                                    delta *= num_subtract;
                                    num_subtract--;
                                    delta = (delta -  p.transition_prob[s][i][j])/num_subtract;
                                    first[j] = false;
                                    j = -1;
                           }
                     }
                     // "not_to_update" states have their probabilities decreased by 'delta'.
                     // If their probability is less than delta, it is zeroed.
                     for (j = 0; j < STATE_NUM; j++) {
                         if (subtract[j] && (p.transition_prob[s][i][j] + precision) >= delta) {
                                  p.transition_prob[s][i][j] -= delta;
                         }
                         else if (subtract[j] && (p.transition_prob[s][i][j] + precision) < delta)  {
                                  p.transition_prob[s][i][j] = 0.0;
                         }
                     }
                     
                     // "to_update" states have their probabilities increased by 'add'.
                     // If their probability, with this addition, would get bigger than 1,
                     // it is set to 1, and 'total' and 'add' values are recomputed.
                     for (k = 0; k < end; k++)   {
                            if ((1.0 - (p.transition_prob[s][i][to_update[k]] + add)) > precision)  {
                               p.transition_prob[s][i][to_update[k]] += add;
                               if (k < end - 1) {
                                  total -= add;
                                  add = total/(end - 1 - k);         // to avoid rounding errors...
                               }

                            }
                            else  {
                               p.transition_prob[s][i][to_update[k]] = 1.0;
                               if (k < end - 1) {
                                  total -= (1.0 -  p.transition_prob[s][i][to_update[k]]);
                                  add = total/(end - 1 - k);
                               }
                            }
                      }

          }

   }
}

// Debugging...
if (parameters.debug_per && updating) {
   for (s = 0; s < STATE_NUM; s++) {
       for(i = 0; i < EM_NUM + 1; i++) {
           for (j = 0; j < STATE_NUM; j++) {
               printf("P_%d(%d, %d) = %.3f\n", s, i, j, p.transition_prob[s][i][j]);
           }
        }
     }
}


// temp
bool b = CheckProb(p);
if (b) PRN(("OK\n"));
else PRN(("NO\n"));

if (updating)
   PRN(("Personality Updated!"));




#ifdef FUNC_TRACK
PRN(("Ending UpdatePersonalityImitating\n"));
#endif

return;

}


// Personality is updated adopting a compensatory attitude
void
UpdatePersonalityCompensating(Personality& p, const Init_per_para& parameters)
{

int i, j, s, k, end, num_update, num_subtract;
int to_update[STATE_NUM];                                       // states whose entry probability will be increased
bool subtract[STATE_NUM];                                       // states whose entry probability will be diminished
bool first[STATE_NUM];
float sum, add, total, delta, precision = 0.000001;             // 'precision' used for comparation of float values
bool updating = false;


#ifdef FUNC_TRACK
PRN(("Starting UpdatePersonalityCompensating\n"));
#endif


// Many nice inputs occured, so we increase transition probabilities to AIBO_SAD and AIBO_ANGRY state
if ((p.nice_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.nice_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_SAD;
   to_update[1] = AIBO_ANGRY;
   end = 2;
   printf("\nI am updating my personality: I will be sadder and angrier.\n\n");
}
// Many sad inputs occured, so we increase transition probabilities to AIBO_JOYFUL state
else if ((p.sad_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.sad_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_JOYFUL;
   end = 1;
   printf("\nI am updating my personality: I will be more joyful.\n\n");
}
// Many bad inputs occured, so we increase transition probabilities to AIBO_JOYFUL state
else if ((p.bad_interaction_points + precision) >= parameters.update_thres) {
   updating = true;
   p.bad_interaction_points = 0.0;                                                 // resetting interaction points
   to_update[0] = AIBO_JOYFUL;
   end = 1;
   printf("\nI am updating my personality: I will be more joyful.\n\n");
}


if (updating) {
   for (i = 0; i < STATE_NUM; i++)
       subtract[i] = true;
   for (k = 0; k < end; k++)
       subtract[to_update[k]] = false;

   for(i = 0; i < EM_NUM + 1; i++) {
         for(s = 0; s < STATE_NUM; s++) {
                     
                     num_update = end;                              // Number of states whose entry probability will be increased
                     num_subtract = STATE_NUM - num_update;         // Number of states whose entry probability will be decreased
                     
                     total = parameters.update_value * num_update;  // Total probability amount to shift to "to_update" states
                                                                        // Every entry probability for those states will be increased,
                                                                        // if possible, by 'parameters.update_value' amount.


                     sum = 0.0;                                     // total amount of probability we can subtract from not_to_update states
                     for (j = 0; j < STATE_NUM; j++) {
                           if (subtract[j]) {
                                 sum += p.transition_prob[s][i][j];
                           }
                     }
                     if ((sum + precision) < total)                // we don't have enough probability! We are going to use
                        total = sum;                               // only the amount that is available

                        
                     delta = total/num_subtract;                   // Amount of probability that will be subtracted from "not_to_update" states
                     add = total/num_update;                       // Amount of probability that will be added to "to_update" states



                     for (j = 0; j < STATE_NUM; j++)
                            first[j] = true;
                     for (j = 0; j < STATE_NUM; j++) {
                           // If state j has entry probability < delta, delta must be recomputed.
                           // Every state will satisfy the if condition at most once, so we use 'first'
                           // array to assure that no state will be considered more than once.
                           if (subtract[j] && first[j] && (p.transition_prob[s][i][j] + precision) < delta) {
                                    delta *= num_subtract;
                                    num_subtract--;
                                    delta = (delta -  p.transition_prob[s][i][j])/num_subtract;
                                    first[j] = false;
                                    j = -1;
                           }
                     }
                     // "not_to_update" states have their probabilities decreased by 'delta'.
                     // If their probability is less than delta, it is zeroed.
                     for (j = 0; j < STATE_NUM; j++) {
                         if (subtract[j] && (p.transition_prob[s][i][j] + precision) >= delta) {
                                  p.transition_prob[s][i][j] -= delta;
                         }
                         else if (subtract[j] && (p.transition_prob[s][i][j] + precision) < delta)  {
                                  p.transition_prob[s][i][j] = 0.0;
                         }
                     }
                     
                     // "to_update" states have their probabilities increased by 'add'.
                     // If their probability, with this addition, would get bigger than 1,
                     // it is set to 1, and 'total' and 'add' values are recomputed.
                     for (k = 0; k < end; k++)   {
                            if ((1.0 - (p.transition_prob[s][i][to_update[k]] + add)) > precision)  {
                               p.transition_prob[s][i][to_update[k]] += add;
                               if (k < end - 1) {
                                  total -= add;
                                  add = total/(end - 1 - k);         // to avoid rounding errors...
                               }

                            }
                            else  {
                               p.transition_prob[s][i][to_update[k]] = 1.0;
                               if (k < end - 1) {
                                  total -= (1.0 -  p.transition_prob[s][i][to_update[k]]);
                                  add = total/(end - 1 - k);
                               }
                            }
                      }

          }

   }
}

// Debugging...
if (parameters.debug_per && updating) {
   for (s = 0; s < STATE_NUM; s++) {
       for(i = 0; i < EM_NUM + 1; i++) {
           for (j = 0; j < STATE_NUM; j++) {
               printf("P_%d(%d, %d) = %.3f\n", s, i, j, p.transition_prob[s][i][j]);
           }
        }
     }
}


// temp
bool b = CheckProb(p);
if (b) PRN(("OK\n"));
else PRN(("NO\n"));

if (updating)
   PRN(("Personality Updated!"));




#ifdef FUNC_TRACK
PRN(("Ending UpdatePersonalityCompensating\n"));
#endif

return;

}

// Checking transition probabilities correctness
bool
CheckProb(const Personality& p)
{

int i, j, s;
float sum, precision = 0.000001;

#ifdef FUNC_TRACK
PRN(("Starting CheckProb\n"));
#endif

for (s = 0; s < STATE_NUM; s++) {
      for (j = 0; j < EM_NUM; j++) {
          sum = 0;
          for (i = 0; i < STATE_NUM; i++) {
              sum += p.transition_prob[s][j][i];
          }
          if (fabs(sum - 1) > precision) {
              return false;
          }
      }
}

#ifdef FUNC_TRACK
PRN(("Ending CheckProb\n"));
#endif

return true;

}

// Freeing Personality memory...
void
FreePersonality(Personality& p)
{

int i, j;

for (i = 0; i < STATE_NUM; i++) {
    for (j = 0; j < STATE_NUM; j++) {
        if (p.transition_prob[i][j] != NULL)
           free(p.transition_prob[i][j]);
    }
    if (p.transition_prob[i] != NULL)
       free(p.transition_prob[i]);
}
if (p.transition_prob != NULL)
   free(p.transition_prob);

p.transition_prob = NULL;

return;

}
