/*
  CommandEquality(Test)  -  verify equality comparisons on command's subsystems

  Copyright (C)         Lumiera.org
    2009,               Hermann Vosseler <Ichthyostega@web.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., 675 Mass Ave, Cambridge, MA 02139, USA.

* *****************************************************/


#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "proc/control/command-def.hpp"
#include "proc/control/command-mutation.hpp"
#include "proc/control/argument-erasure.hpp"
#include "proc/control/command-argument-holder.hpp"
#include "proc/control/memento-tie.hpp"
#include "lib/meta/tuple.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"

#include <functional>
#include <iostream>
#include <string>

namespace proc {
namespace control {
namespace test    {
  
  
  
  using namespace lib::meta;
  using lib::Symbol;
  using util::isnil;
  using util::isSameObject;
  
  using std::function;
  using std::bind;
  using std::string;
  using std::cout;
  using std::endl;
  
  
  
  namespace {
    
    string check_;
    
    Symbol COMMAND1 ("test.equalityCommand1");
    Symbol COMMAND2 ("test.equalityCommand2");
    
    const string MARK_1 ("|_1_");
    const string MARK_2 ("|_2_");
    
    void oper_1 (char par)             { check_ += MARK_1 + par; }
    void oper_2 (char par)             { check_ += MARK_2 + par; }
    
    string capt_1 (char par)           { return check_ + MARK_1 + "|"+par+"|"; }
    string capt_2 (char par)           { return check_ + MARK_2 + "|"+par+"|"; }
    
    void undo_1 (char par, string mem) { check_ = mem + MARK_1 + par + "|";}
    void undo_2 (char par, string mem) { check_ = mem + MARK_2 + par + "|";}
    
    
    typedef void   Sig_oper(char);
    typedef string Sig_capt(char);
    typedef void   Sig_undo(char,string);
    
    typedef function<Sig_oper> Fun_o;
    typedef function<Sig_capt> Fun_c;
    typedef function<Sig_undo> Fun_u;
    
    typedef Tuple<Types<char> > ArgTuple;
    typedef Closure<Sig_oper> ArgHolder;
    typedef MementoTie<Sig_oper, string> MemHolder;
  }
  
  
  
  /*************************************************************************************//**
   * @test cover command equality detection. Two commands are deemed equivalent, if they
   *       - build on the same Mutation functors
   *       - are either both incomplete or
   *       - are bound to equivalent arguments
   *       - hold equivalent undo state (memento)
   * To conduct this test, we set up two sets of functions, and then build both complete
   * command objects and command implementation facilities based on them.
   * 
   * @note The hidden problem with those comparisons is the equivalence of function objects.
   *       While required by TR1, unfortunately lib boost refuses to implement functor equality.
   *       Which forces us to resort to a low level hack, based on internals of the boost function
   *       implementation. This workaround reliably pinpoints differing functions, but sometimes
   *       fails to detect equivalent functions under specific circumstances (e.g. when there is
   *       binding involved, and / or the binders have been cloned). Bottom line: \c == is
   *       reliable, \c != might be wrong.
   * 
   * @see  control::Command
   * @see  control::CmdClosure
   * @see  control::Mutation
   * @see  control::UndoMutation
   * @see  control::MementoTie
   * @see  control::CommandImpl
   * @see  command-basic-test.hpp
   * @see  functor-util.hpp functor equality workaround
   */
  class CommandEquality_test : public Test
    {
      
      virtual void
      run (Arg) 
        {
          CHECK (&oper_1 != &oper_2);
          CHECK (&capt_1 != &capt_2);
          CHECK (&undo_1 != &undo_2);
          
          verifyMutationEquality();
          verifyMementoEquality();
          verifyClosureEquality();
          verifyCommandEquality();
        }
      
      
      
      void
      verifyMutationEquality()
        {
          Fun_o oFun_1 (oper_1);
          Fun_o oFun_2 (oper_2);
          Fun_o oFun_empty;
          
          Fun_u uFun_1 (undo_1);
          Fun_u uFun_empty;
          
          Mutation mut1 (oFun_1);
          Mutation muti (oFun_1);
          Mutation mut2 (oFun_2);
          CHECK (mut1 == mut1);
          CHECK (mut1 == muti);
          CHECK (muti == mut1);
          CHECK (mut1 != mut2);
          CHECK (mut2 != mut1);
          CHECK (muti != mut2);
          CHECK (mut2 != muti);
          
          Mutation umu (oFun_empty);    // empty operation function
          CHECK (mut1 != umu);
          
          Mutation mut_u0 (uFun_empty); // empty undo function
          CHECK (mut_u0 != umu);
          CHECK (mut_u0 != muti);
          
          Mutation mut_u1 (uFun_1); 
          CHECK (mut_u0 != mut_u1);    // function signatures differing
        }
      
      
      void
      verifyClosureEquality()
        {
          ArgHolder a1 (tuple::make ('a'));
          ArgHolder a2 (tuple::make ('z'));
          CHECK (a1 == a1);
          CHECK (a1 != a2);
          CHECK (a2 != a1);
          
          TypedArguments<ArgTuple> newArgs (tuple::make ('z'));
          a1.bindArguments(newArgs);
          CHECK (a1 == a2);
          CHECK (a2 == a1);
          
          typedef ArgumentHolder<Sig_oper,string> AHImpl;
          AHImpl abuff1;
          AHImpl abuff2;
          CHECK (abuff1 == abuff2);
          abuff1.bindArguments(newArgs);
          CHECK (abuff1 != abuff2);
          abuff2.bindArguments(newArgs);
          CHECK (abuff1 == abuff2);
          UndoMutation umu1 (abuff1.tie (undo_1, capt_1));
          CHECK (abuff1 != abuff2);                       // abuff2 isn't tied yet, i.e. has no undo/capture function
          UndoMutation umu2 (abuff2.tie (undo_1, capt_1));
          CHECK (abuff1 == abuff2);                       // same capture function, no memento state!
          
          umu1.captureState(a1);
          CHECK (abuff1 != abuff2);
          umu2.captureState(a1);
          CHECK (abuff1 == abuff2); // same functions, same memento state
          
          check_ += "fake";          // manipulate the "state" to be captured
          umu2.captureState(a1);     // capture again...
          CHECK (abuff1 != abuff2); // captured memento differs!
          
          UndoMutation umu3 (abuff2.tie (undo_1, capt_2));
          umu3.captureState(a1);
          CHECK (abuff1 != abuff2); // differing functions detected
        }
      
      
      void
      verifyMementoEquality()
        {
          Fun_u uFun_1 (undo_1);
          Fun_u uFun_2 (undo_2);
          Fun_c cFun_1 (capt_1);
          Fun_c cFun_2 (capt_2);
          Fun_c cFun_empty;
          
          Fun_c empty_c;
          
          MemHolder m11 (uFun_1, cFun_1);
          MemHolder m12 (uFun_1, cFun_2);
          MemHolder m21 (uFun_2, cFun_empty);   // note: unbound capture function
          MemHolder m22 (uFun_2, cFun_2);
          
          CHECK (m11 == m11);
          CHECK (m12 == m12);
          CHECK (m21 == m21);
          CHECK (m22 == m22);
          CHECK (!(m11 != m11));
          
          CHECK (m11 != m12);
          CHECK (m11 != m21);
          CHECK (m11 != m22);
          CHECK (m12 != m11);
          CHECK (m12 != m21);
          CHECK (m12 != m22);
          CHECK (m21 != m11);
          CHECK (m21 != m12);
          CHECK (m21 != m22);
          CHECK (m22 != m11);
          CHECK (m22 != m12);
          CHECK (m22 != m21);
          
          MemHolder m22x (m22); // clone copy
          CHECK (!m22x);
          CHECK (m22 == m22x); // same functions, no state --> equal
          
          m22x.tieCaptureFunc() ('x');   // produce a memento state
          CHECK (!isnil (m22x.getState()));
          
          CHECK (m22 != m22x);
          m22.tieCaptureFunc() ('x'); // get same value into the memento within m22
          CHECK (m22 == m22x);
          
          // document shortcomings on UndoMutation comparisons
          UndoMutation umu11 (m11);
          UndoMutation umu12 (m11);    // note: due to cloning the embedded functor,
          CHECK (umu11 != umu12);     //       our hacked-in comparison operator fails
        }
      
      
      void
      verifyCommandEquality()
        {
          CommandDef (COMMAND1)
            .operation (oper_1)
            .captureUndo (capt_1)
            .undoOperation (undo_1)
            ;
          CommandDef (COMMAND2)
            .operation (oper_2)
            .captureUndo (capt_2)
            .undoOperation (undo_2)
            ;
          
          Command c1 = Command::get(COMMAND1);
          Command c2 = Command::get(COMMAND2);
          CHECK (c1 == c1);
          CHECK (c1 != c2);
          CHECK (c2 != c1);
          
          Command cx = c1;
          CHECK (c1 == cx);
          CHECK (cx == c1);
          CHECK (!isSameObject (c1, c2));
          
          // verify equality matches behaviour
          string protocol1 = execCommand(c1);
          string protocolX = execCommand(cx);
          string protocol2 = execCommand(c2);
          
          CHECK (protocol1 == protocolX);
          CHECK (protocol1 != protocol2);
        }
      
      
      /** Helper: invoke and undo a command, 
       *  @return resulting operation protocol
       */
      string
      execCommand (Command com)
        {
          check_ = "(start)";
          com.bind('o');
          com();
          cout << com << ":" << check_ << endl;
          com.undo();
          cout << com << ":" << check_ << endl;
          return check_;
        }
    };
  
  
  /** Register this test class... */
  LAUNCHER (CommandEquality_test, "function controller");
  
  
}}} // namespace proc::control::test
