// flipflop.hxx - implementation of multiple flip flop types
//
// Written by Torsten Dreyer
//
// Copyright (C) 2010  Torsten Dreyer - Torsten (at) t3r (dot) de
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//

#include "flipflop.hxx"
#include "functor.hxx"
#include <simgear/misc/inputvalue.hxx>
#include <Main/fg_props.hxx>

using std::map;
using std::string;
using std::endl;
using std::cout;

namespace FGXMLAutopilot {

/**
 * @brief Flip flop implementation for a RS flip flop with dominant RESET
 *
 * RS (reset-set) flip flops act as a fundamental latch. It has two input lines, 
 * S (set) and R (reset). Activating the set input sets the output while activating
 * the reset input resets the output. If both inputs are activated, the output
 * is deactivated, too. This is why the RESET line is called dominant. Use a
 * SRFlipFlopImplementation for a dominant SET line.
 *
 * <table>
 * <tr>
 * <td colspan="3">Logictable</td>
 * </tr>
 * <tr>
 *   <td>S</td><td>R</td><td>Q</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>unchanged</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>true</td><td>false</td>
 * </tr>
 * <tr>
 *   <td>true</td><td>false</td><td>true</td>
 * </tr>
 * <tr>
 *   <td>true</td><td>true</td><td>false</td>
 * </tr>
 * </table>
 */
class RSFlipFlopImplementation : public FlipFlopImplementation {
protected:
  bool _rIsDominant;
public:
  RSFlipFlopImplementation( bool rIsDominant = true ) : _rIsDominant( rIsDominant ) {}
  virtual bool getState( double dt, DigitalComponent::InputMap input, bool & q );
};

/**
 * @brief Flip flop implementation for a RS flip flop with dominant SET
 *
 * SR (set-reset) flip flops act as a fundamental latch. It has two input lines, 
 * S (set) and R (reset). Activating the set input sets the output while activating
 * the reset input resets the output. If both inputs are activated, the output
 * is activated, too. This is why the SET line is called dominant. Use a
 * RSFlipFlopImplementation for a dominant RESET line.
 * 
 * <table>
 * <tr>
 * <td colspan="3">Logictable</td>
 * </tr>
 * <tr>
 *   <td>S</td><td>R</td><td>Q</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>unchanged</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>true</td><td>false</td>
 * </tr>
 * <tr>
 *   <td>true</td><td>false</td><td>true</td>
 * </tr>
 * <tr>
 *   <td>true</td><td>true</td><td>true</td>
 * </tr>
 * </table>
 */
class SRFlipFlopImplementation : public RSFlipFlopImplementation {
public:
  SRFlipFlopImplementation() : RSFlipFlopImplementation( false ) {}
};

/**
 * @brief Base class for clocked flip flop implementation
 *
 * A clocked flip flop computes it's output on the raising edge (false/true transition)
 * of the clock input. If such a transition is detected, the onRaisingEdge method is called
 * by this implementation. All clocked flip flops inherit from the RS flip flop and may
 * be set or reset by the respective set/reset lines. Note that the RS implementation 
 * ignores the clock, The output is set immediately, regardless of the state of the clock
 * input. The "clock" input is mandatory for clocked flip flops.
 * 
 */
class ClockedFlipFlopImplementation : public RSFlipFlopImplementation {
private:
  /** 
   * @brief the previous state of the clock input 
   */
  bool _clock;
protected:

  /**
   * @brief pure virtual function to be implemented from the implementing class, gets called
   * from the update method if the raising edge of the clock input was detected. 
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool onRaisingEdge( DigitalComponent::InputMap input, bool & q ) = 0;
public:

  /**
   * @brief constructor for a ClockedFlipFlopImplementation
   * @param rIsDominant boolean flag to signal if RESET shall be dominant (true) or SET shall be dominant (false)
   */
  ClockedFlipFlopImplementation( bool rIsDominant = true ) : RSFlipFlopImplementation( rIsDominant ), _clock(false) {}

  /**
   * @brief evaluates the output state from the input lines. 
   * This method basically waits for a raising edge and calls onRaisingEdge
   * @param dt the elapsed time in seconds from since the last call
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool getState( double dt, DigitalComponent::InputMap input, bool & q );
};

/**
 * @brief Implements a JK flip flop as a clocked flip flop
 *
 * The JK flip flop has five input lines: R, S, clock, J and K. The R and S lines work as described
 * in the RS flip flop. Setting the J line to true sets the output to true on the next raising
 * edge of the clock line. Setting the K line to true sets the output to false on the next raising
 * edge of the clock line. If both, J and K are true, the output is toggled at with every raising
 * edge of the clock line. 
 *
 * Undefined inputs default to false.
 *
 * <table>
 * <tr>
 * <td colspan="7">Logictable</td>
 * </tr>
 * <tr>
 *   <td>S</td><td>R</td><td>J</td><td>K</td><td>clock</td><td>Q (previous)</td><td>Q</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>false</td><td>false</td><td>any</td><td>any</td><td>unchanged</td>
 * </tr>
 * <tr>
 *   <td>true</td><td>false</td><td>any</td><td>any</td><td>any</td><td>any</td><td>true</td>
 * </tr>
 * <tr>
 *   <td>any</td><td>true</td><td>any</td><td>any</td><td>any</td><td>any</td><td>false</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>true</td><td>false</td><td>^</td><td>any</td><td>true</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>false</td><td>true</td><td>^</td><td>any</td><td>false</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>true</td><td>true</td><td>^</td><td>false</td><td>true</td>
 * </tr>
 * <tr>
 *   <td>false</td><td>false</td><td>true</td><td>true</td><td>^</td><td>true</td><td>false</td>
 * </tr>
 * </table>
 */
class JKFlipFlopImplementation : public ClockedFlipFlopImplementation {
public:
  /**
   * @brief constructor for a JKFlipFlopImplementation
   * @param rIsDominant boolean flag to signal if RESET shall be dominant (true) or SET shall be dominant (false)
   */
  JKFlipFlopImplementation( bool rIsDominant = true ) : ClockedFlipFlopImplementation ( rIsDominant ) {}

  /**
   * @brief compute the output state according to the logic table on the raising edge of the clock
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool onRaisingEdge( DigitalComponent::InputMap input, bool & q );
};

/** 
 * @brief Implements a D (delay) flip flop.
 *
 */
class DFlipFlopImplementation : public ClockedFlipFlopImplementation {
public:
  /**
   * @brief constructor for a DFlipFlopImplementation
   * @param rIsDominant boolean flag to signal if RESET shall be dominant (true) or SET shall be dominant (false)
   */
  DFlipFlopImplementation( bool rIsDominant = true ) : ClockedFlipFlopImplementation ( rIsDominant ) {}

  /**
   * @brief compute the output state according to the logic table on the raising edge of the clock
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool onRaisingEdge( DigitalComponent::InputMap input, bool & q ) {
    q = input.get_value("D");
    return true;
  }
};

/** 
 * @brief Implements a T (toggle) flip flop.
 *
 */
class TFlipFlopImplementation : public ClockedFlipFlopImplementation {
public:
  /**
   * @brief constructor for a TFlipFlopImplementation
   * @param rIsDominant boolean flag to signal if RESET shall be dominant (true) or SET shall be dominant (false)
   */
  TFlipFlopImplementation( bool rIsDominant = true ) : ClockedFlipFlopImplementation ( rIsDominant ) {}

  /**
   * @brief compute the output state according to the logic table on the raising edge of the clock
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool onRaisingEdge( DigitalComponent::InputMap input, bool & q ) {
    q = !q;
    return true;
  }
};

/** 
 * @brief Implements a monostable flip flop
 *
 * The stable output state is false.
 *
 */
class MonoFlopImplementation : public JKFlipFlopImplementation {
protected:
  virtual bool configure( SGPropertyNode& cfg_node,
                          const std::string& cfg_name,
                          SGPropertyNode& prop_root );
  simgear::ValueList _time;
  double _t;

  bool isConfigProperty(const std::string& cfg_name) const override
  {
    return cfg_name == "time";
  }

public:
  /**
   * @brief constructor for a MonoFlopImplementation
   * @param rIsDominant boolean flag to signal if RESET shall be dominant (true) or SET shall be dominant (false)
   */
  MonoFlopImplementation( bool rIsDominant = true ) : JKFlipFlopImplementation( rIsDominant ), _t(0.0) {}
  /**
   * @brief evaluates the output state from the input lines and returns to the stable state 
   * after expiry of the internal timer
   * @param dt the elapsed time in seconds from since the last call
   * @param input a map of named input lines
   * @param q a reference to a boolean variable to receive the output state
   * @return true if the state has changed, false otherwise
   */
  virtual bool getState( double dt, DigitalComponent::InputMap input, bool & q );
};

} // namespace

using namespace FGXMLAutopilot;

//------------------------------------------------------------------------------
bool MonoFlopImplementation::configure( SGPropertyNode& cfg_node,
                                        const std::string& cfg_name,
                                        SGPropertyNode& prop_root )
{
  if( JKFlipFlopImplementation::configure(cfg_node, cfg_name, prop_root) )
    return true;

  if (cfg_name == "time") {
    _time.push_back(new simgear::Value(prop_root, cfg_node));
    return true;
  } 

  return false;
}

bool MonoFlopImplementation::getState( double dt, DigitalComponent::InputMap input, bool & q )
{
  if( JKFlipFlopImplementation::getState( dt, input, q ) ) {
    _t = q ? _time.get_value() : 0;
    return true;
  }

  _t -= dt;
  if( _t <= 0.0 ) {
    q = 0;
    return true;
  }

  return false;
}


bool RSFlipFlopImplementation::getState( double dt, DigitalComponent::InputMap input, bool & q )
{
  bool s = input.get_value("S");
  bool r = input.get_value("R");

  // s == false && q == false: no change, keep state
  if( s || r ) {
    if( _rIsDominant ) { // RS: reset is dominant
      if( s ) q = true; // set
      if( r ) q = false; // reset
    } else { // SR: set is dominant
      if( r ) q = false; // reset
      if( s ) q = true; // set
    }
    return true; // signal state changed
  }
  return false; // signal state unchagned
}

bool ClockedFlipFlopImplementation::getState( double dt, DigitalComponent::InputMap input, bool & q )
{
  bool c = input.get_value("clock");
  bool raisingEdge = c && !_clock;
    
  _clock = c;

  if( RSFlipFlopImplementation::getState( dt, input, q ) )
    return true;


  if( !raisingEdge ) return false; //signal no change
  return onRaisingEdge( input, q );
}

bool JKFlipFlopImplementation::onRaisingEdge( DigitalComponent::InputMap input, bool & q )
{
  bool j = input.get_value("J");
  bool k = input.get_value("K");
    
  // j == false && k == false: no change, keep state
  if( (j || k) ) {
    if( j && k ) {
      q = !q; // toggle
    } else {
      if( j ) q = true;  // set
      if( k ) q = false; // reset
    }
    return true; // signal state changed
  }

  return false; // signal no change
}

//------------------------------------------------------------------------------
bool FlipFlopImplementation::configure( SGPropertyNode& prop_root,
                                        SGPropertyNode& cfg )
{
  for( int i = 0; i < cfg.nChildren(); ++i )
  {
    SGPropertyNode_ptr child = cfg.getChild(i);
    string cname(child->getNameString());

    if( configure(*child, cname, prop_root) )
      continue;
  }

  return true;
}


static map<string,FunctorBase<FlipFlopImplementation> *> componentForge;

//------------------------------------------------------------------------------
bool FlipFlop::configure( SGPropertyNode& cfg_node,
                          const std::string& cfg_name,
                          SGPropertyNode& prop_root )
{ 
  if( componentForge.empty() ) {
    componentForge["RS"] = new CreateAndConfigureFunctor<RSFlipFlopImplementation,FlipFlopImplementation>();
    componentForge["SR"] = new CreateAndConfigureFunctor<SRFlipFlopImplementation,FlipFlopImplementation>();
    componentForge["JK"] = new CreateAndConfigureFunctor<JKFlipFlopImplementation,FlipFlopImplementation>();
    componentForge["D"]  = new CreateAndConfigureFunctor<DFlipFlopImplementation, FlipFlopImplementation>();
    componentForge["T"]  = new CreateAndConfigureFunctor<TFlipFlopImplementation, FlipFlopImplementation>();
    componentForge["monostable"]  = new CreateAndConfigureFunctor<MonoFlopImplementation, FlipFlopImplementation>();
  }

  if( DigitalComponent::configure(cfg_node, cfg_name, prop_root) )
    return true;

  if( cfg_name == "type" ) {
    string type(cfg_node.getStringValue());
    if( componentForge.count(type) == 0 ) {
      SG_LOG
      (
        SG_AUTOPILOT,
        SG_BULK,
        "unhandled flip-flop type <" << type << ">"
      );
      return true;
    }
    _implementation = (*componentForge[type])(prop_root, *cfg_node.getParent());
    return true;
  }

  if (cfg_name == "set"||cfg_name == "S") {
    _input["S"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  }

  if (cfg_name == "reset" || cfg_name == "R" ) {
    _input["R"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  } 

  if (cfg_name == "J") {
    _input["J"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  } 

  if (cfg_name == "K") {
    _input["K"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  } 

  if (cfg_name == "D") {
    _input["D"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  } 

  if (cfg_name == "clock") {
    _input["clock"] = sgReadCondition(&prop_root, &cfg_node);
    return true;
  }

  if (_implementation && _implementation->isConfigProperty(cfg_name)) {
    // componentForge CreateAndConfigureFunctor<> already called configure with all the nodes
    // on our implementation, but we need to ensure we don't return false here for such nodes
    return true;
  }

  return false; 
}

void FlipFlop::update( bool firstTime, double dt )
{
  if( _implementation == NULL ) {
    SG_LOG( SG_AUTOPILOT, SG_ALERT, "No flip-flop implementation for " << subsystemId() << endl );
    return;
  }

  bool q0, q;

  q0 = q = get_output();

  if( _implementation->getState( dt, _input, q ) && q0 != q ) {
    set_output( q );

    if(_debug) {
      cout << "updating flip-flop \"" << subsystemId() << "\"" << endl;
      cout << "prev. Output:" << q0 << endl;
      for( InputMap::const_iterator it = _input.begin(); it != _input.end(); ++it ) 
        cout << "Input \"" << (*it).first << "\":" << (*it).second->test() << endl;
      cout << "new Output:" << q << endl;
    }
  }
}


// Register the subsystem.
SGSubsystemMgr::Registrant<FlipFlop> registrantFlipFlop;
