/*
  HashIndexed(Test)  -  proof-of-concept test for a hash based and typed ID

  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/hash-indexed.hpp"
#include "lib/util.hpp"

#include <unordered_map>

using util::isSameObject;


namespace lib {
namespace test{
  
  /* ==  a hierarchy of test-dummy objects to use the HashIndexed::ID == */
  
  struct DummyAncestor
    {
      long xyz_;
    };
  
  struct TestB;                                  ///< Base class to mix in the hash ID facility
  typedef HashIndexed<TestB, hash::LuidH> Mixin; ///< actual configuration of the mixin
  
  struct TestB : DummyAncestor, Mixin
    {
      TestB () {}
      TestB (ID const& refID) : Mixin (refID) {}
      
      bool operator== (TestB const& o)  const { return this->getID() == o.getID(); }
    };
  struct TestDA : TestB {};
  struct TestDB : TestB {};
  
  
  
  
  /***********************************************************************//**
   * @test proof-of-concept test for a generic hash based and typed ID struct.
   *       - check the various ctors 
   *       - check default assignment works properly
   *       - check assumptions about memory layout
   *       - check equality comparison
   *       - extract LUID and then cast LUID back into ID
   *       - use the embedded hash ID (LUID) as hashtable key
   *       
   * @see lib::HashIndexed::Id
   * @see mobject::Placement real world usage example 
   */
  class HashIndexed_test : public Test
    {
      
      virtual void
      run (Arg)
        {
          checkBasicProperties();
          checkLUID_passing();
          
          //            ---key-type-------+-value-+-hash-function--- 
          buildHashtable<TestB::Id<TestDB>, TestDB, TestB::UseHashID> ();
          buildHashtable<TestDB,            TestDB, TestB::UseEmbeddedHash>();
        }
      
      
      void
      checkBasicProperties ()
        {
          TestB::Id<TestDA> idDA;
          
          TestB bb (idDA);
          
          TestB::Id<TestDB> idDB1 ;
          TestB::Id<TestDB> idDB2 (idDB1);
          
          CHECK (sizeof (idDB1)     == sizeof (idDA) );
          CHECK (sizeof (TestB::ID) == sizeof (hash::LuidH));
          CHECK (sizeof (TestDA)    == sizeof (hash::LuidH) + sizeof (DummyAncestor));
          
          CHECK (idDA  == bb.getID() );
          CHECK (idDB1 == idDB2 );            // equality handled by the hash impl (here LuidH)
          
          TestDA d1;
          TestDA d2;
          CHECK (d1.getID() != d2.getID());   // should be different because LUIDs are random
          
          d2 = d1; 
          CHECK (d1.getID() == d2.getID());   // default assignment operator works as expected
        }
      
      
      void
      checkLUID_passing ()
        {
          TestB::Id<TestDA> idOrig;
          
          lumiera_uid plainLUID;
          lumiera_uid_copy (&plainLUID, idOrig.get());
          
          // now, maybe after passing it through a Layer barrier...
          TestB::ID const& idCopy = reinterpret_cast<TestB::ID & > (plainLUID);
          
          CHECK (idOrig == idCopy);
        }
      
      
      template<class KEY, class VAL, class HashFunc>
      void
      buildHashtable ()
        {
          typedef std::unordered_map<KEY, VAL, HashFunc> Hashtable;
          
          Hashtable tab;
          
          VAL o1;  KEY key1 (o1);
          VAL o2;  KEY key2 (o2);
          VAL o3;  KEY key3 (o3);
          
          tab[key1] = o1;                             // store copy into hashtable
          tab[key2] = o2;
          tab[key3] = o3;
          
          CHECK (!isSameObject (o1, tab[key1]));     // indeed a copy...
          CHECK (!isSameObject (o2, tab[key2]));
          CHECK (!isSameObject (o3, tab[key3]));
          
          CHECK (o1.getID() == tab[key1].getID());   // but "equal" by ID
          CHECK (o2.getID() == tab[key2].getID());
          CHECK (o3.getID() == tab[key3].getID());
          
          CHECK (o1.getID() != tab[key2].getID());
          CHECK (o1.getID() != tab[key3].getID());
          CHECK (o2.getID() != tab[key3].getID());
        }
      
    };
  
  
  /** Register this test class... */
  LAUNCHER (HashIndexed_test, "unit common");
  
  
}} // namespace lib::test
