/****************************************************************************
 * This file was derived from the CNCL library by the Pablo research group.
 *
 * The complete CNCL library is available at:
 *       http://www.comnets.rwth-aachen.de/doc/cncl.html
 *
 * The CNCL copyright notice is included here:
 *
 * |_|_|_  |_|_    |_    |_|_|_  |_               C O M M U N I C A T I O N
 *|_       |_  |_  |_  |_        |_                         N E T W O R K S
 *|_       |_  |_  |_  |_        |_                               C L A S S
 * |_|_|_  |_    |_|_    |_|_|_  |_|_|_|_                     L I B R A R Y
 *
 *
 *
 * Copyright (C) 1992-1998   Communication Networks
 *                           Aachen University of Technology
 *                           D-52056 Aachen
 *                           Germany
 *                           Email: cncl-adm@comnets.rwth-aachen.de
 *
 *
 * This library is free  software; you can  redistribute it and/or modify
 * it  under   the terms  of the GNU   Library  General Public License as
 * published by the  Free Software Foundation;  either  version 2 of  the
 * License, or  (at  your option)  any  later  version.  This  library is
 * distributed  in the  hope  that  it  will  be useful,  but WITHOUT ANY
 * WARRANTY;  without even the   implied  warranty of MERCHANTABILITY  or
 * FITNESS FOR A PARTICULAR PURPOSE.   See the GNU Library General Public
 * Library General Public License along with  this library; if not, write
 * to the Free Software Foundation,  675  Mass Ave, Cambridge, MA  02139,
 * USA.
 ****************************************************************************/

//   -*- C++ -*-
/*****************************************************************************
 *
 * Class: FlFRuleBase --- Rule base and Fuzzy inference engine
 *
 *****************************************************************************/

#include "FlFRuleBase.h"
#include "FlFSetTrapez.h"

#include <Flminmax.h>

using std::cerr;
using std::endl;
using std::ifstream;

/*
 * Debug output for rules
 */
void FlFRuleBase::debug_rules(ostream &strm, int /*lvl*/)
{
    size_t i;
    FlFRule *r;
    
    for(i = 0; i < rules.get_size(); i++)
    {
        r = (FlFRule *) (rules[i]);
        strm << "Rule " << i << ": " << r->aggregate_value() << endl;
    }
}

/*
 * Add rule to base
 */
void FlFRuleBase::add_rule(FlFRule *rule)
{
    int n = rules.get_size();
    
    rules.set_size(n + 1);
    rules[n] = rule;
}

void FlFRuleBase::add_rule(FlFRule &rule)
{
    add_rule(&rule);
}

/*
 * Add input variable to base
 */
void FlFRuleBase::add_in_var(FlFVar *var)
{
    int n = in_vars.get_size();
    
    in_vars.set_size(n + 1);
    in_vars[n] = var;
}

void FlFRuleBase::add_in_var(FlFVar &var)
{
    add_in_var(&var);
}

/*
 * Add output variable to base
 */
void FlFRuleBase::add_out_var(FlFVar *var)
{
    int n = out_vars.get_size();
    
    out_vars.set_size(n + 1);
    out_vars[n] = var;
}

void FlFRuleBase::add_out_var(FlFVar &var)
{
    add_out_var(&var);
}

/*
 * Get sizes of arrays
 */
int FlFRuleBase::get_n_rules() const
{
    return rules.get_size();
}

int FlFRuleBase::get_n_in_vars() const
{
    return in_vars.get_size();
}

int FlFRuleBase::get_n_out_vars() const
{
    return out_vars.get_size();
}

int FlFRuleBase::resolution() const
{
   return nsteps;
}

/*
 * Compute inference for variable var and value set, using match value
 * from rule LHS aggregation. Result is stored in an array fuzzy set.
 */
void FlFRuleBase::inference(FlFVar *var, FlFSet *set, double match, FlFSetArray &res)
{
   double xmin, xmax, x, dx;
   int i, n;
    
   n = res.get_n();
   xmin = var->xmin();
   xmax = var->xmax();
   dx = (xmax - xmin)/n;

   /**FIXME: this should use the inference operator**/
   for(i = 0, x = xmin; i < n; i++, x += dx)
   {
      res[i] = match*set->get_membership(x);
   }
}

/*
 * Compute output fuzzy set for variable var
 */
void FlFRuleBase::evaluate(FlFVar *var, FlFSetArray &result)
{
   int i, j, k, m, n;
   double agg;
   FlFRule *rule;
   FlFClause *clause;
   FlFSetArray tmp(result.get_n(), var->xmin(), var->xmax());
    
   /*
    * Rules must be aggregated beforehand!
    */

   n = get_n_rules();
    
   /*
    * Search all rules
    */
   for(i = 0; i < n; i++)
   {
      rule = (FlFRule *)( rules[i] );
      agg = rule->aggregate_value();
      if(agg > 0)                                // Rule fired
      {
         m = rule->get_n_rhs();
         /*
          * Search all variables in RHS of rule
          */
         for(j = 0; j < m; j++)
         {
            clause = rule->get_rhs(j);
            if (clause->var == var)                // Found variable
            {
               inference(var, clause->value, agg, tmp);
               /**FIXME: this should use the accumulation operator**/
               for(k = 0; k < result.get_n(); k++)
               {
                  result[k] = Flmax(tmp[k], result[k]);
               }
            }
         }
      }  
   }
}

/*
 * Aggregate all rules
 */
void FlFRuleBase::aggregate_all()
{
   for(int i = 0; i < get_n_rules(); i++) 
   {
      ((FlFRule *)(rules[i]))->aggregate();
   }
}

/*
 * Evaluate all variables
 */
void FlFRuleBase::evaluate_all()
{
   int i;
   FlFVar *var;
   FlFSetArray *set;

   aggregate_all();
    
   for(i = 0; i < get_n_out_vars(); i++)
   {
      var = (FlFVar *)(out_vars[i]);
      if(var->fuzzy_value()) 
      {
         delete var->fuzzy_value();
      }
      set = new FlFSetArray(var->name() + "-Out", resolution(), var->xmin(), var->xmax());
      evaluate(var, *set);
      var->fuzzy_value(set);
   }
}

/*
 * Defuzzify output variables using center_of_gravity()
 */
void FlFRuleBase::defuzzy_all()
{
   int i;
   FlFVar *var;
   FlFSet *set;

   for(i=0; i<get_n_out_vars(); i++)
   {
      var = (FlFVar *)(out_vars[i]);
      if( (set = var->fuzzy_value()) ) 
      {
         var->value( set->center_of_gravity( var->xmin(), var->xmax() ) );
      }
   }
}
    

/***** Default I/O member function for FuzzyLib classes **************************/

// Normal output
void FlFRuleBase::print(ostream &strm) const
{
    int i;

    strm << "rulebase " << name() << ";" << endl;
    
    strm << "// ----- INPUT -----" << endl;
    for(i=0; i<get_n_in_vars(); i++)
        strm << in_vars.get(i);
    strm << "// ----- OUTPUT -----" << endl;
    for(i=0; i<get_n_out_vars(); i++)
        strm << out_vars.get(i);
    strm << "// ----- RULES -----" << endl;
    for(i=0; i<get_n_rules(); i++)
        strm << rules.get(i);
}

// Debug output
void FlFRuleBase::dump(ostream &strm) const
{
    strm << "FlFRuleBase { $Revision: 1.6 $"
         << " }" << endl;
}

// This method receives as argument the name of the file that contains the new
// fuzzy sets
Fl_bool FlFRuleBase::updateFuzzySets(const char* filename)
{
   FlArrayObject new_vars;
   Fl_bool returnValue;

   ifstream input_file; 
   
   input_file.open(filename);
   if (!input_file.good())
   {
      cerr << "Cannot load new fuzzy sets: Error in file " << filename << endl;
      return FALSE;
   }

   // Parse the file
   returnValue = parseInputFile(input_file, &new_vars);
   if (returnValue == FALSE)
   {
      cerr << "Cannot load fuzzy sets: Syntax error in file " << filename << endl;
      return FALSE;
   }

   // Update the sets
   returnValue =  updateSets(new_vars);
   if (returnValue == FALSE)
   {
      cerr << "Cannot load fuzzy sets: Format error in file " << filename << endl;
      return FALSE;
   }
   return TRUE;
}

// This method parses the fuzzy sets file and returns an FlArrayObject which
// includes all fuzzy variables found in the file
//FlArrayObject FlFRuleBase::parseInputFile(ifstream &input_file)
Fl_bool FlFRuleBase::parseInputFile(ifstream &input_file, FlArrayObject* new_vars)
{
//   FlArrayObject new_vars;
   FlFVar *new_var;
   FlFSetLR *new_set;

   char buffer[1024];
   char *s;
   enum States state = START;
   int n;

   // Read the lines of the file
   while (input_file.getline(buffer, 1024))
   {
      // Read the first word of the line
      s = strtok(buffer, " ;\t\n");

      // Skip the blank lines
      if (s == NULL) 
      {
         continue;
      }

      // Skip the comments
      if (strncmp(s, "//", 2) == 0)
      {
         continue;
      }

      // Parse the sets file
      switch(state)
      {
         case START:          // Find the rulebase
            if (strcasecmp(s, "rulebase") == 0)
            {
               s = strtok(NULL, " ;\t\n");
               if (strcasecmp(s, name()) != 0)
               {
                  // Error: rulebase name does not match
                  cerr << "File format error: rulebase name does not match" << endl;
                  return FALSE;
               }
               state = RULEBASE;
            }
            else
            {
               // Error: Missing rulebase declaration
               cerr << "File format error: Missing rulebase declaration" << endl;
               return FALSE;
            }
            break;
         case RULEBASE:       // Now, find the sets
            if (strcasecmp(s, "var") == 0)
            {
               char *var_name;
               double var_min, var_max;

               var_name = strtok(NULL, " (;\t\n");
               s = strtok(NULL, " (,\t\n");
               var_min = atof(s);
               s = strtok(NULL, " ),\t\n");
               var_max = atof(s);
               new_var = new FlFVar(var_name, var_min, var_max);
               state = VAR_BEGIN;
            }
            else
            {
               // Error: Missing variable definitions
               cerr << "File format error: Missing variable definitions" << endl;
               return FALSE;
            }
            break;
         case VAR_BEGIN:      // Find the opening bracket
            if (strncmp(s, "{", 1) == 0)
            {
               state = SET;
            }
            else
            {
               // Error: Incorrect set definition
               cerr << "File format error: incorrect set definition" << endl;
               return FALSE;
            }
            break;
         case SET:            // Find the sets
            if (strcasecmp(s, "set") == 0)
            {
               char *set_name;
               char *set_type;
               set_type = strtok(NULL, " (\t\n");
               if (strcasecmp(set_type, "triangle") == 0) 
               {
                  set_name = strtok(NULL, " (\t\n");
                  double xm = atof(strtok(NULL, " (,\t\n"));
                  double xalpha = atof(strtok(NULL, " (,\t\n"));
                  double xbeta = atof(strtok(NULL, " (),\t\n"));
   
                  new_set = new FlFSetTrapez(set_name, xm, xm, xalpha, xbeta);
                  new_var->add_value_set(new_set);
               }
               else if (strcasecmp(set_type, "trapez") == 0)
               {
                  set_name = strtok(NULL, " (\t\n");
                  double xm1 = atof(strtok(NULL, " (,\t\n"));
                  double xm2 = atof(strtok(NULL, " (,\t\n"));
                  double xalpha = atof(strtok(NULL, " (,\t\n"));
                  double xbeta = atof(strtok(NULL, " (),\t\n"));

                  new_set = new FlFSetTrapez(set_name, xm1, xm2, xalpha, xbeta);
                  new_var->add_value_set(new_set);
               }
               else
               {
                  // Error: Incorrect set type
                  cerr << "File format error: incorrect set type" << endl;
                  return FALSE;
               }
            } 
            else if (strncmp(s, "}", 1) == 0)
            {
               // If the closing bracket is found, look for a new rulebase
               state = RULEBASE;

               n = new_vars->get_size();
               new_vars->set_size(n + 1);
               new_vars->put(n, new_var);
            } 
            else
            {
               // Error: Incorrect set definition
               cerr << "File format error: incorrect set definition" << endl;
               return FALSE;
            }
            break;
         default:
            cerr << "File format error: Format error" << endl;
            return FALSE;
      }
   }
   input_file.close();

   return TRUE;
}

// This method receives a FlArrayObject containing all new variables with
// their respective fuzzy sets, and matches them to the existing ones. If
// the sets match, they are updated with the new values.
Fl_bool FlFRuleBase::updateSets(FlArrayObject new_vars)
{
   FlFVar* new_var;
   FlFVar* in_var;
   FlFVar* out_var;
   FlFSetLR* new_set;
   FlFSetLR* in_set;
   FlFSetLR* out_set;
   Fl_bool var_matches;
   Fl_bool set_matches;
   int j, k, l;

   // For all the variables read from the file
   for (int i = 0; i < new_vars.get_size(); i++)
   {
      var_matches = FALSE;
      new_var = (FlFVar*) new_vars[i];
 
      // compare their names to all the input variables in the rulebase
      for (j = 0; j <in_vars.get_size(); j++)
      {
         in_var = (FlFVar *) in_vars[j];
         // If one of them matches, proceed to compare the set names
         if (new_var->get_name() == in_var->get_name())
         {
            var_matches = TRUE;
            FlArrayObject* in_varsets = in_var->get_varsets();
            FlArrayObject* new_varsets = new_var->get_varsets();

            // Get the corresponding set names read from the file
            for (k = 0; k < new_varsets->get_size(); k++)
            {
               new_set = (FlFSetLR *) new_varsets->get(k);
               set_matches = FALSE;

               // Compare them to the set names in the input variable
               for (l = 0; l < in_varsets->get_size(); l++)
               {
                  in_set = (FlFSetLR *) in_varsets->get(l);
                  // If they match, replace the set
                  if (in_set->get_name() == new_set->get_name())
                  {
                     set_matches = TRUE;
                     in_set->update(new_set->get_m1(), new_set->get_m2(), 
                        new_set->get_alpha(), new_set->get_beta());
                     break;
                  }
               }
               if (!set_matches)
               {
                  cerr << "File Format Error: Error in set " << new_set->get_name()
                     << endl;
                  return FALSE;
               }
            }
            break; 
         }
      }

      // If a match was found, continue. Else, check the output variables
      if (var_matches)
      {
         continue;
      }

      // compare their names to all the output variables in the rulebase
      for (j = 0; j < out_vars.get_size(); j++)
      {
         out_var = (FlFVar *) out_vars[j];
         // If one of them matches, proceed to compare the set names
         if (new_var->get_name() == out_var->get_name())
         {
            var_matches = TRUE;
            FlArrayObject* out_varsets = out_var->get_varsets();
            FlArrayObject* new_varsets = new_var->get_varsets();

            // Get the corresponding set names read from the file
            for (k = 0; k < new_varsets->get_size(); k++)
            {
               new_set = (FlFSetLR *) new_varsets->get(k);
               set_matches = FALSE;

               // Compare them to the set names in the input variable
               for (l = 0; l < out_varsets->get_size(); l++)
               {
                  out_set = (FlFSetLR *) out_varsets->get(l);
                  // If they match, replace the set
                  if (new_set->get_name() == out_set->get_name())
                  {
                     set_matches = TRUE;
                     out_set->update(new_set->get_m1(), new_set->get_m2(), 
                        new_set->get_alpha(), new_set->get_beta());
                     break;
                  }
               }
               if (!set_matches)
               {
                  cerr << "File Format Error: Error in set " << new_set->get_name() 
                     << endl;
                  return FALSE;
               }
            }
            break;
         }
      }

      if (!var_matches)
      {
         cerr << "File Format Error: Error in variable " << new_var->get_name() << endl;
         return FALSE;
      }
   }
   return TRUE;
}
