/*
  HashStandardToBoostBridge(Test)  -  verify a workaround to combine both systems of hash function definition

  Copyright (C)         Lumiera.org
    2014,               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-standard.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/util.hpp"

#include <boost/functional/hash.hpp>
#include <unordered_set>
#include <iostream>
#include <string>
#include <vector>

using util::contains;
using std::unordered_set;
using std::vector;
using std::string;
using std::cout;
using std::endl;


namespace lib {
namespace test{
  
  /* ==  custom data items, each with its own private hash function == */
  
  /**
   * Custom-Datatype 1:
   * uses std::hash extension point
   */
  class S
    {
      string s_;
      
      friend std::hash<S>;
      
    public:
      S(string ss ="")
        : s_(ss)
        { }
      
      bool
      operator== (S const& os)  const
        {
          return s_ == os.s_;
        }
    };
  
}}
// this is ugly: we have to close our own namespace and open namespace std
// for the sole purpose of placing our private hash function into the official extension point
  
namespace std {
  
  using lib::test::S;
  
  template<>
  struct hash<S>
    {
      size_t
      operator() (S const& val)  const noexcept
        {
          hash<string> string_hasher;
          return string_hasher(val.s_);
        }
    };
  
} // and back again into our own namespace...
namespace lib {
namespace test{
  
  
  
  
  /**
   * Custom-Datatype 2:
   * uses boost::hash extension point
   */
  class V
    {
      vector<string> v_;
      
    public:
      V(string ss ="")
        {
          v_.push_back(ss);
        }
      
      bool
      operator== (V const& ov)  const
        {
          return v_ == ov.v_;
        }
      
      friend size_t
      hash_value (V const& v)
      {
        return boost::hash_value(v.v_);
      }
    };
  
  
  
  
  /***********************************************************************//**
   * @test document and verify an automatic bridge to integrate boost-style
   *       hash functions with the new \c std::hash template.
   *       Currently (as of 2014) this requires us to "hijack" the standard
   *       library definition to defeat a static assertion, which otherwise
   *       would defeat any attempts based on SFINAE
   * 
   * This test builds two custom types, one with a std::hash compliant
   * hash function extension point, the other one with a boost::hash style
   * custom hash function, to be picked up by ADL. By virtue of the automatic
   * bridging template, we're able to use the boost-style definition at places
   * where the std::hash is expected -- most notably in STL hashtable containers.
   * 
   * @see hash-standard.hpp
   * @see lib::HashIndexed
   */
  class HashStandardToBoostBridge_test : public Test
    {
      
      virtual void
      run (Arg)
        {
          checkHashFunctionInvocation();
          checkHashtableUsage();
        }
      
      
      void
      checkHashFunctionInvocation ()
        {
          string p("Путин"), pp(p);
          S s(p), ss(pp);
          V v(p), vv(pp);
          
          std::hash<string>   std_stringHasher;
          boost::hash<string> boo_stringHasher;
          
          std::hash<S>   std_customHasher;
          boost::hash<V> boo_customHasher;
          
          CHECK(0 < std_stringHasher(p));
          CHECK(0 < boo_stringHasher(p));
          CHECK(0 < std_customHasher(p));
          CHECK(0 < boo_customHasher(p));
          
          CHECK(std_stringHasher(p) == std_stringHasher(pp));
          CHECK(boo_stringHasher(p) == boo_stringHasher(pp));
          CHECK(std_customHasher(s) == std_customHasher(ss));
          CHECK(boo_customHasher(v) == boo_customHasher(vv));
          
          // should be equal too, since the custom impl delegates to the standard string hasher
          CHECK(std_stringHasher(p) == std_customHasher(s));
          
          
          // verify the trait used to build the automatic bridge
          CHECK(! lib::meta::provides_BoostHashFunction<S>::value);
          CHECK(  lib::meta::provides_BoostHashFunction<V>::value);
          
          //verify the automatic bridge
          std::hash<V> boo2std_crossHar;
          CHECK(0 < boo2std_crossHar(v));
          
          // the std hash function delegates to the boost function...
          CHECK(boo2std_crossHar(v) == boo_customHasher(v));
          
//  Note: does not compile,
//        since there is not automatic bridge to use std::hash from boost::hash
//  
//        boost::hash<S>()(s);
        }
      
      
      void
      checkHashtableUsage ()
        {
          string o1{randStr(5)},
                 o2{randStr(6)},
                 o3{randStr(7)},
                 o4{randStr(8)};
          S s1{o1}, s2{o2}, s3{o3}, s4{o4}, s5{o2}, s6{o1};
          V v1{o1}, v2{o2}, v3{o3}, v4{o4}, v5{o3}, v6{o2};
          
          unordered_set<S> us{s1,s2,s3,s4,s5,s6};
          unordered_set<V> uv{v1,v2,v3,v1,v5,v6};
          
          CHECK(4 == us.size());
          CHECK(3 == uv.size());
          
          CHECK(contains (us, s1));
          CHECK(contains (us, s2));
          CHECK(contains (us, s3));
          CHECK(contains (us, s4));
          CHECK(contains (us, s5));
          CHECK(contains (us, s6));
          
          CHECK( contains (uv, v1));
          CHECK( contains (uv, v2));
          CHECK( contains (uv, v3));
          CHECK(!contains (uv, v4));
          CHECK( contains (uv, v5));
          CHECK( contains (uv, v6));
        }
    };
  
  
  /** Register this test class... */
  LAUNCHER (HashStandardToBoostBridge_test, "unit common");
  
  
}} // namespace lib::test
