//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// 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 "T3D/components/game/stateMachine.h"

StateMachine::StateMachine()
{
   mStateStartTime = -1;
   mStateTime = 0;

   mStartingState = "";

   mCurCreateState = NULL;

   mStateMachineFile = StringTable->EmptyString();

   mCurCreateState = nullptr;
}

StateMachine::~StateMachine()
{
}

void StateMachine::loadStateMachineFile()
{
   if (!mXMLReader)
   {
      SimXMLDocument *xmlrdr = new SimXMLDocument();
      xmlrdr->registerObject();

      mXMLReader = xmlrdr;
   }

   bool hasStartState = false;

   if (!dStrIsEmpty(mStateMachineFile))
   {
      //use our xml reader to parse the file!
      SimXMLDocument *reader = mXMLReader.getObject();
      if (!reader->loadFile(mStateMachineFile))
         Con::errorf("Could not load state machine file: &s", mStateMachineFile);

      if (!reader->pushFirstChildElement("StateMachine"))
         return;

      //find our starting state
      if (reader->pushFirstChildElement("StartingState"))
      {
         mStartingState = reader->getData();
         reader->popElement();
         hasStartState = true;
      }

      readStates();
   }

   if (hasStartState)
      mCurrentState = getStateByName(mStartingState);

   mStateStartTime = -1;
   mStateTime = 0;
}

void StateMachine::readStates()
{
   SimXMLDocument *reader = mXMLReader.getObject();

   //iterate through our states now!
   if (reader->pushFirstChildElement("State"))
   {
      //get our first state
      State firstState;

      readStateName(&firstState, reader);
      readStateScriptFunction(&firstState, reader);

      readTransitions(firstState);

      mStates.push_back(firstState);

      //now, iterate the siblings
      while (reader->nextSiblingElement("State"))
      {
         State newState;
         readStateName(&newState, reader);
         readStateScriptFunction(&newState, reader);

         readTransitions(newState);

         mStates.push_back(newState);
      }
   }
}

void StateMachine::readTransitions(State &currentState)
{
   SimXMLDocument *reader = mXMLReader.getObject();

   //iterate through our states now!
   if (reader->pushFirstChildElement("Transition"))
   {
      //get our first state
      StateTransition firstTransition;

      readTransitonTarget(&firstTransition, reader);

      readConditions(firstTransition);

      currentState.mTransitions.push_back(firstTransition);

      //now, iterate the siblings
      while (reader->nextSiblingElement("Transition"))
      {
         StateTransition newTransition;
         readTransitonTarget(&newTransition, reader);

         readConditions(newTransition);

         currentState.mTransitions.push_back(newTransition);
      }

      reader->popElement();
   }
}

void StateMachine::readConditions(StateTransition &currentTransition)
{
   SimXMLDocument *reader = mXMLReader.getObject();

   //iterate through our states now!
   if (reader->pushFirstChildElement("Rule"))
   {
      //get our first state
      StateTransition::Condition firstCondition;
      StateField firstField;
      bool fieldRead = false;
      
      readFieldName(&firstField, reader);
      firstCondition.field = firstField;

      readFieldComparitor(&firstCondition, reader);

      readFieldValue(&firstCondition.field, reader);

      currentTransition.mTransitionRules.push_back(firstCondition);

      //now, iterate the siblings
      while (reader->nextSiblingElement("Transition"))
      {
         StateTransition::Condition newCondition;
         StateField newField;

         readFieldName(&newField, reader);
         newCondition.field = newField;

         readFieldComparitor(&newCondition, reader);

         readFieldValue(&newCondition.field, reader);

         currentTransition.mTransitionRules.push_back(newCondition);
      }

      reader->popElement();
   }
}

S32 StateMachine::parseComparitor(const char* comparitorName)
{
   S32 targetType = -1;

   if (!dStrcmp("GreaterThan", comparitorName))
      targetType = StateMachine::StateTransition::Condition::GeaterThan;
   else if (!dStrcmp("GreaterOrEqual", comparitorName))
      targetType = StateMachine::StateTransition::Condition::GreaterOrEqual;
   else if (!dStrcmp("LessThan", comparitorName))
      targetType = StateMachine::StateTransition::Condition::LessThan;
   else if (!dStrcmp("LessOrEqual", comparitorName))
      targetType = StateMachine::StateTransition::Condition::LessOrEqual;
   else if (!dStrcmp("Equals", comparitorName))
      targetType = StateMachine::StateTransition::Condition::Equals;
   else if (!dStrcmp("True", comparitorName))
      targetType = StateMachine::StateTransition::Condition::True;
   else if (!dStrcmp("False", comparitorName))
      targetType = StateMachine::StateTransition::Condition::False;
   else if (!dStrcmp("Negative", comparitorName))
      targetType = StateMachine::StateTransition::Condition::Negative;
   else if (!dStrcmp("Positive", comparitorName))
      targetType = StateMachine::StateTransition::Condition::Positive;
   else if (!dStrcmp("DoesNotEqual", comparitorName))
      targetType = StateMachine::StateTransition::Condition::DoesNotEqual;

   return targetType;
}

void StateMachine::update()
{
   //we always check if there's a timout transition, as that's the most generic transition possible.
   F32 curTime = Sim::getCurrentTime();

   if (mStateStartTime == -1)
      mStateStartTime = curTime;

   mStateTime = curTime - mStateStartTime;

   char buffer[64];
   dSprintf(buffer, sizeof(buffer), "%g", mStateTime);

   checkTransitions("stateTime", buffer);
}

void StateMachine::checkTransitions(const char* slotName, const char* newValue)
{
   //because we use our current state's fields as dynamic fields on the instance
   //we'll want to catch any fields being set so we can treat changes as transition triggers if
   //any of the transitions on this state call for it

   //One example would be in order to implement burst fire on a weapon state machine.
   //The behavior instance has a dynamic variable set up like: GunStateMachine.burstShotCount = 0;

   //We also have a transition in our fire state, as: GunStateMachine.addTransition("FireState", "burstShotCount", "DoneShooting", 3);
   //What that does is for our fire state, we check the dynamicField burstShotCount if it's equal or greater than 3. If it is, we perform the transition.

   //As state fields are handled as dynamicFields for the instance, regular dynamicFields are processed as well as state fields. So we can use the regular 
   //dynamic fields for our transitions, to act as 'global' variables that are state-agnostic. Alternately, we can use state-specific fields, such as a transition
   //like this:
   //GunStateMachine.addTransition("IdleState", "Fidget", "Timeout", ">=", 5000);

   //That uses the the timeout field, which is reset each time the state changes, and so state-specific, to see if it's been 5 seconds. If it has been, we transition
   //to our fidget state

   //so, lets check our current transitions
   //now that we have the type, check our transitions!
   for (U32 t = 0; t < mCurrentState.mTransitions.size(); t++)
   {
      //if (!dStrcmp(mCurrentState.mTransitions[t]., slotName))
      {
         //found a transition looking for this variable, so do work
         //first, figure out what data type thie field is
         //S32 type = getVariableType(newValue);

         bool fail = false;
         bool match = false;
         S32 ruleCount = mCurrentState.mTransitions[t].mTransitionRules.size();

         for (U32 r = 0; r < ruleCount; r++)
         {
            const char* fieldName = mCurrentState.mTransitions[t].mTransitionRules[r].field.name;
            if (!dStrcmp(fieldName, slotName))
            {
               match = true;
               //now, check the value with the comparitor and see if we do the transition.
               if (!passComparitorCheck(newValue, mCurrentState.mTransitions[t].mTransitionRules[r]))
               {
                  fail = true;
                  break;
               }
            }
         }

         //If we do have a transition rule for this field, and we didn't fail on the condition, go ahead and switch states
         if (match && !fail)
         {
            setState(mCurrentState.mTransitions[t].mStateTarget);

            return;
         }
      }
   }
}

bool StateMachine::passComparitorCheck(const char* var, StateTransition::Condition transitionRule)
{
   F32 num = dAtof(var);
   switch (transitionRule.field.fieldType)
   {
   case StateField::Type::VectorType:
      switch (transitionRule.triggerComparitor)
      {
      case StateTransition::Condition::Equals:
      case StateTransition::Condition::GeaterThan:
      case StateTransition::Condition::GreaterOrEqual:
      case StateTransition::Condition::LessThan:
      case StateTransition::Condition::LessOrEqual:
      case StateTransition::Condition::DoesNotEqual:
         //do
         break;
      default:
         return false;
      };
   case StateField::Type::StringType:
      switch (transitionRule.triggerComparitor)
      {
      case StateTransition::Condition::Equals:
         if (!dStrcmp(var, transitionRule.field.triggerStringVal))
            return true;
         else
            return false;
      case StateTransition::Condition::DoesNotEqual:
         if (dStrcmp(var, transitionRule.field.triggerStringVal))
            return true;
         else
            return false;
      default:
         return false;
      };
   case StateField::Type::BooleanType:
      switch (transitionRule.triggerComparitor)
      {
      case StateTransition::Condition::TriggerValueTarget::True:
         if (dAtob(var))
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::False:
         if (dAtob(var))
            return false;
         else
            return true;
      default:
         return false;
      };
   case StateField::Type::NumberType:
      switch (transitionRule.triggerComparitor)
      {
      case StateTransition::Condition::TriggerValueTarget::Equals:
         if (num == transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::GeaterThan:
         if (num > transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::GreaterOrEqual:
         if (num >= transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::LessThan:
         if (num < transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::LessOrEqual:
         if (num <= transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::DoesNotEqual:
         if (num != transitionRule.field.triggerNumVal)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::Positive:
         if (num > 0)
            return true;
         else
            return false;
      case StateTransition::Condition::TriggerValueTarget::Negative:
         if (num < 0)
            return true;
         else
            return false;
      default:
         return false;
      };
   default:
      return false;
   };
}

void StateMachine::setState(const char* stateName, bool clearFields)
{
   State oldState = mCurrentState;
   StringTableEntry sName = StringTable->insert(stateName);
   for (U32 i = 0; i < mStates.size(); i++)
   {
      //if(!dStrcmp(mStates[i]->stateName, stateName))
      if (!dStrcmp(mStates[i].stateName,sName))
      {
         mCurrentState = mStates[i];
         mStateStartTime = Sim::getCurrentTime();

         onStateChanged.trigger(this, i);
         return;
      }
   }
}

const char* StateMachine::getStateByIndex(S32 index)
{
   if (index >= 0 && mStates.size() > index)
      return mStates[index].stateName;
   else
      return "";
}

StateMachine::State& StateMachine::getStateByName(const char* name)
{
   StringTableEntry stateName = StringTable->insert(name);

   for (U32 i = 0; i < mStates.size(); i++)
   {
      if (!dStrcmp(stateName, mStates[i].stateName))
         return mStates[i];
   }
}

S32 StateMachine::findFieldByName(const char* name)
{
   for (U32 i = 0; i < mFields.size(); i++)
   {
      if (!dStrcmp(mFields[i].name, name))
         return i;
   }

   return -1;
}