/*
  FormatString(Test)  -  validate string template formatting (printf style)

  Copyright (C)         Lumiera.org
    2011,               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 "lib/format-string.hpp"
#include "lib/symbol.hpp"
#include "lib/error.hpp"
#include "lib/util.hpp"

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <cstdlib>

using boost::lexical_cast;
using std::cout;
using std::endl;


namespace util {
namespace test {
  
  namespace error = lumiera::error;
  
  
  
  /***************************************************************************//**
   * @test verifies our front-end for printf-style format string based formatting.
   *       - the implementation is based on boost::format
   *       - we create a wrapper-object on-the fly, which is able to hold
   *         the result of a partial invocation, until all parameters are given.
   *       - explicit specialisations feed all primitive types directly down
   *         into the implementation level. If possible, a custom operator string()
   *         will be used for formatting.
   *       - exceptions will be logged, but handled gracefully
   * 
   * @see format-string.hpp
   * @see boost::format
   */
  class FormatString_test : public Test
    {
      void
      run (Arg)
        {
          check_simpleInvocation();
          check_delayedInvocation();
          verify_typeHandling();
          verify_customType();
          verify_errorHandling();
          verify_pointerHandling();
        }
      
      
      
      void
      check_simpleInvocation ()
        {
          string formatted = _Fmt("--format-template--int=%04d--double=%+5.2f--string=%-9s--")
                                 % 12
                                 % 1.228
                                 % "Lumiera";
          cout << formatted << endl;
          CHECK (formatted == "--format-template--int=0012--double=+1.23--string=Lumiera  --");
        }
      
      
      void
      check_delayedInvocation ()
        {
          string formatString = "%p %|20T_| %u";
          _Fmt formatter (formatString);
          
          uint val = rand() % 100;
          void *pt = &val;
          
          formatter % pt;
          formatter % val;
          
          cout << formatter << endl;
        }
      
      
      void
      verify_typeHandling ()
        {
          int i(-12);
          CHECK (_Fmt("%d") % i     == "-12"    );
          CHECK (_Fmt("%6d") % i    == "   -12" );
          CHECK (_Fmt("%-6d") % i   == "-12   " );
          CHECK (_Fmt("%+-6d") % -i == "+12   " );
          CHECK (_Fmt("%+06d") % -i == "+00012" );
          CHECK (_Fmt("%06X") % -i  == "00000C" );
          CHECK (_Fmt("%#x") % -i   == "0xc"    );
          
          uint u(12);
          CHECK (_Fmt("%d") % u     == "12"     );
          CHECK (_Fmt("%6d") % u    == "    12" );
          CHECK (_Fmt("%-6d") % u   == "12    " );
          CHECK (_Fmt("%-+6d") % u  == "12    " );
          CHECK (_Fmt("%+06d") % u  == "000012" );
          
          short sh(-123);
          CHECK (_Fmt("%6d") % sh   == "  -123" );
          
          ushort ush(123);
          CHECK (_Fmt("%6d") % ush  == "   123" );
          
          long l(-123);
          CHECK (_Fmt("%6d") % l    == "  -123" );
          
          ulong ul(123);
          CHECK (_Fmt("%6d") % ul   == "   123" );
          
          int64_t ll(5e+9);
          CHECK (_Fmt("%d") % ll    == "5000000000" );
          CHECK (_Fmt("%5.3d") %ll  == "5000000000" );
          CHECK (_Fmt("%10.3e") %ll == "5000000000" );
          
          uint64_t ull(ll);
          CHECK (_Fmt("%d") % ull   == "5000000000" );
          
          float f(12.34);
          CHECK (_Fmt("%g")   % f   == "12.34"  );
          CHECK (_Fmt("%d")   % f   == "12.34"  );
          CHECK (_Fmt("%.3f") % f   == "12.340" );
          CHECK (_Fmt("%.1e") % f   == "1.2e+01");
          CHECK (_Fmt("%.0f") % f   == "12"     );
          
          double d(-12.34);
          CHECK (_Fmt("%g") % d     == "-12.34" );
          
          char c(0x40);
          CHECK (_Fmt("%d") % c        == "@"   );
          CHECK (_Fmt("%x") % c        == "@"   );
          CHECK (_Fmt("%o") % c        == "@"   );
          CHECK (_Fmt("%c") % c        == "@"   );
          CHECK (_Fmt("%s") % c        == "@"   );
          CHECK (_Fmt("%d") % short(c) == "64"  );
          CHECK (_Fmt("%x") % short(c) == "40"  );
          CHECK (_Fmt("%o") % short(c) == "100" );
          CHECK (_Fmt("%c") % short(c) == "6"   );
          CHECK (_Fmt("%s") % short(c) == "64"  );
          
          uchar uc(0xff);
          CHECK (_Fmt("%d") % uint(uc) == "255" );
          CHECK (_Fmt("%x") % uint(uc) == "ff"  );
          CHECK (_Fmt("%X") % uint(uc) == "FF"  );
          CHECK (_Fmt("%c") % uint(uc) == "2"   );
          CHECK (_Fmt("%s") % uint(uc) == "255" );
          CHECK (_Fmt("%d") % uc       == "\xFF");
          CHECK (_Fmt("%x") % uc       == "\xFF");
          CHECK (_Fmt("%X") % uc       == "\xFF");
          CHECK (_Fmt("%c") % uc       == "\xFF");
          CHECK (_Fmt("%s") % uc       == "\xFF");
          
          c = char(uc);
          CHECK (_Fmt("%c") % c        == "\xFF");
          
          string str("Lumiera");
          CHECK (_Fmt("%s") % str   == "Lumiera"    );
          CHECK (_Fmt("%10s") % str == "   Lumiera" );
          CHECK (_Fmt("%7.4s") %str == "   Lumi"    );
          CHECK (_Fmt("%10c") % str == "         L" );
          
          const char* pch("edit");
          CHECK (_Fmt("%s") %   pch == "edit"       );
          CHECK (_Fmt("%10s") % pch == "      edit" );
          CHECK (_Fmt("%7.3s") %pch == "    edi"    );
          CHECK (_Fmt("%10c") % pch == "         e" );
          
          lib::Literal lit("your");
          CHECK (_Fmt("%s") %   lit == "your"       );
          CHECK (_Fmt("%10s") % lit == "      your" );
          CHECK (_Fmt("%7.2s") %lit == "     yo"    );
          CHECK (_Fmt("%10c") % lit == "         y" );
          
          lib::Symbol sym("freedom");
          CHECK (_Fmt("%s") %   sym == "freedom"    );
          CHECK (_Fmt("%10s") % sym == "   freedom" );
          CHECK (_Fmt("%7.5s") %sym == "  freed"    );
          CHECK (_Fmt("%10c") % sym == "         f" );
        }
      
      
      /* == some custom types to test with == */
      
      struct Silent
        {
          int i_;
          
          Silent(int i) : i_(i) { }
        };
      
      struct Verbose
        : Silent
        {
          Verbose(int i) : Silent(i) { }
          
          virtual
          operator string()  const
            {
              return _Fmt("Number-%03d") % i_; 
            }
        };
      
      struct Explosive
        : Verbose
        {
          Explosive(int i) : Verbose(i) { }
          
          operator string()  const
            {
              if (23 == i_)
                throw error::State("encountered Fantomas");
              else
                return Verbose::operator string();
            }
        };
      
      
      void
      verify_customType ()
        {
          Silent s(12);
          Verbose v(13);
          Explosive x(23);
          
          Silent& rs1 = v;
          Silent& rs2 = x;
          
          Verbose& rv = x;
          
          string mangledType = _Fmt("%s") % s;
          cout << mangledType << endl;
          CHECK (contains (mangledType, "util"));
          CHECK (contains (mangledType, "test"));
          CHECK (contains (mangledType, "Silent"));
          
          CHECK (_Fmt("!!%s!!") % v  == "!!Number-013!!");
          CHECK (_Fmt("!!%s!!") % x  == "!!<string conversion failed: LUMIERA_ERROR_STATE:unforeseen state (encountered Fantomas).>!!");
          
          CHECK (contains (_Fmt("%s") % rs1, "Silent"));
          CHECK (contains (_Fmt("%s") % rs2, "Silent"));
          
          CHECK (_Fmt("!!%s!!") % rv  == "!!<string conversion failed: LUMIERA_ERROR_STATE:unforeseen state (encountered Fantomas).>!!");
          
          x.i_ = 42;
          CHECK (_Fmt("!!%s!!") % rv  == "!!Number-042!!");
          CHECK (_Fmt("!!%s!!") % x   == "!!Number-042!!");
          
          lib::Symbol sym("42");
          CHECK (_Fmt("!!%s!!") % sym == "!!42!!");   // but especially Symbol datatype is explicitly treated like a string
        }
      
      
      void
      verify_errorHandling ()
        {
          cout << _Fmt("__nix_") % 1 % 2 << endl;
          cout << _Fmt("__%d__")         << endl;
          cout << _Fmt("__%d__") % 1     << endl;
          cout << _Fmt("__%d__") % 1 % 2 << endl;
          
          const char* evil = NULL;
          cout << _Fmt("__%d__") %  evil  << endl;
          cout << _Fmt("__%d__") % "dirt" << endl;
          cout << _Fmt("__%d__") % "1234" << endl;
          cout << _Fmt("__%d__") % "0xff" << endl;
          
          VERIFY_ERROR(FORMAT_SYNTAX, _Fmt("%broken"));
        }
      
      
      void
      verify_pointerHandling ()
        {
          int             i(-12);     int       *   pi = &  i;
          uint             u(12);     uint      *   pu = &  u;
          short         sh(-123);     short     *  psh = & sh;
          ushort        ush(123);     ushort    * push = &ush;
          long           l(-123);     long      *   pl = &  l;
          ulong          ul(123);     ulong     *  pul = & ul;
          int64_t       ll(5e+9);     int64_t   *  pll = & ll;
          uint64_t       ull(ll);     uint64_t  * pull = &ull;
          float         f(12.34);     float     *   pf = &  f;
          double       d(-12.34);     double    *   pd = &  d;
          uchar         uc(0xff);     uchar     *  puc = & uc;
          string  str("Lumiera");     string    * pstr = &str;
          
          CHECK (_Fmt("%d") %   i == _Fmt("%d") %   pi);
          CHECK (_Fmt("%d") %   u == _Fmt("%d") %   pu);
          CHECK (_Fmt("%d") %  sh == _Fmt("%d") %  psh);
          CHECK (_Fmt("%d") % ush == _Fmt("%d") % push);
          CHECK (_Fmt("%d") %   l == _Fmt("%d") %   pl);
          CHECK (_Fmt("%d") %  ul == _Fmt("%d") %  pul);
          CHECK (_Fmt("%d") %  ll == _Fmt("%d") %  pll);
          CHECK (_Fmt("%d") % ull == _Fmt("%d") % pull);
          CHECK (_Fmt("%e") %   f == _Fmt("%e") %   pf);
          CHECK (_Fmt("%e") %   d == _Fmt("%e") %   pd);
          CHECK (_Fmt("%g") %  uc == _Fmt("%g") %  puc);
          CHECK (_Fmt("%s") % str == _Fmt("%s") % pstr);
          
          char c('a');
          char pc[2] = {'a', '\0'};
          CHECK (_Fmt("%g") %   c == _Fmt("%g") %   pc);
          
          Verbose v(42);
          Verbose *pv = &v;
          void    *vv = pv;
          CHECK (_Fmt("__%s__") % v  == "__Number-042__");
          CHECK (_Fmt("__%s__") % pv == "__Number-042__");
          CHECK (_Fmt("__%s__") % vv != "__Number-042__");
          
          string address = lexical_cast<string>(pv);
          CHECK (_Fmt("__%s__") % vv == "__"+address+"__");
          
          pv = NULL;
          vv = NULL;
          CHECK (_Fmt("__%s__") % pv == "__<null>__");
          CHECK (_Fmt("__%s__") % vv == "__0__");
        }
    };
  
  LAUNCHER (FormatString_test, "unit meta");
  
  
}} // namespace util::test

