/*
  Copyright (c) 1998 - 2024
  ILK   - Tilburg University
  CLST  - Radboud University
  CLiPS - University of Antwerp

  This file is part of timbl

  timbl 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 3 of the License, or
  (at your option) any later version.

  timbl 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, see <http://www.gnu.org/licenses/>.

  For questions and suggestions, see:
      https://github.com/LanguageMachines/timbl/issues
  or send mail to:
      lamasoftware (at ) science.ru.nl

*/

#ifndef TIMBL_TYPES_H
#define TIMBL_TYPES_H

#include <string>
#include <set>
#include <vector>
#include <map>
#include <exception>
#include <stdexcept>
#include <ostream>
#include <sstream>

#include "ticcutils/StringOps.h"
#include "timbl/StringOps.h"

namespace Timbl {
  enum InputFormatType { UnknownInputFormat,
			 Compact, C4_5, Columns, Tabbed, ARFF, SparseBin,
			 Sparse,
			 MaxInputFormat };

  inline InputFormatType& operator++( InputFormatType &I ){
    return I = ( MaxInputFormat == I )
      ? UnknownInputFormat
      : InputFormatType(I+1);
  }


  enum WeightType { Unknown_w,
		    No_w, GR_w, IG_w, X2_w, SV_w, SD_w, UserDefined_w,
		    Max_w };

  inline WeightType& operator++( WeightType &W ){
    return W = ( Max_w == W ) ? Unknown_w : WeightType(W+1);
  }

  enum AlgorithmType { Unknown_a,
		       IB1_a, IB2_a, IGTREE_a, TRIBL_a, TRIBL2_a,
		       LOO_a, CV_a,
		       Max_a };

  inline AlgorithmType& operator++( AlgorithmType &W ){
    return W = ( Max_a == W ) ? Unknown_a : AlgorithmType(W+1);
  }


  enum MetricType { UnknownMetric, Ignore,
		    Numeric, DotProduct, Cosine, Overlap, Levenshtein,
		    Dice, ValueDiff, JeffreyDiv, JSDiv, Euclidean, MaxMetric };

  inline MetricType& operator++( MetricType &W ){
    return W = ( MaxMetric == W ) ? UnknownMetric : MetricType(W+1);
  }

  enum OrdeningType { UnknownOrdening, DataFile, NoOrder,
		      GROrder, IGOrder,
		      OneoverFeature, OneoverSplitInfo,
		      GRoverFeature, IGoverFeature,
		      GREntropyOrder, IGEntropyOrder,
		      X2Order, SVOrder, SDOrder,
		      X2overFeature, SVoverFeature, SDoverFeature,
		      MaxOrdening };

  inline OrdeningType& operator++( OrdeningType &W ){
    return W = ( MaxOrdening == W ) ? UnknownOrdening : OrdeningType(W+1);
  }


  enum VerbosityFlags { NO_VERB=0, SILENT=1, OPTIONS=2, FEAT_W=4,
			VD_MATRIX=8, EXACT=16, DISTANCE=32, DISTRIB=64,
			NEAR_N=128, ADVANCED_STATS=256, CONF_MATRIX=512,
			CLASS_STATS=1024, CLIENTDEBUG=2048, ALL_K=4096,
			MATCH_DEPTH=8192, BRANCHING=16384, CONFIDENCE=32768,
                        MAX_VERB };

  inline VerbosityFlags operator~( VerbosityFlags V ){
    return (VerbosityFlags)( ~(int)V );
  }

  inline VerbosityFlags operator|( VerbosityFlags V1, VerbosityFlags V2 ){
    return (VerbosityFlags)( (int)V1|(int)V2 );
  }

  inline VerbosityFlags& operator|= ( VerbosityFlags& f, VerbosityFlags g ){
    f = (f | g);
    return f;
  }

  inline VerbosityFlags operator& ( VerbosityFlags f, VerbosityFlags g ){
    return (VerbosityFlags)((int)f & (int)g );
}

  inline VerbosityFlags& operator&= ( VerbosityFlags& f, VerbosityFlags g ){
    f = (f & g);
    return f;
  }

  enum OptType { UnknownOpt,
		 StringT, IntegerT, BooleanT, VerbosityT, IFormatT,
		 AlgoT, MetricT, WeightT, OrdeningT,
		 MaxOpt };

  inline OptType& operator++( OptType &W ){
    return W = ( MaxOpt == W ) ? UnknownOpt : OptType(W+1);
  }

  enum DecayType { UnknownDecay,
		   Zero, InvDist, InvLinear, ExpDecay, MaxDecay };

  inline DecayType& operator++( DecayType &W ){
    return W = ( MaxDecay == W ) ? UnknownDecay : DecayType(W+1);
  }

  enum SmoothingType { UnknownSmoothing, Default, Lidstone, MaxSmoothing };

  inline SmoothingType& operator++( SmoothingType &W ){
    return W = ( MaxSmoothing == W ) ? UnknownSmoothing : SmoothingType(W+1);
  }

  enum normType { unknownNorm, noNorm, probabilityNorm,
		  addFactorNorm, logProbNorm, maxNorm };

  inline normType& operator++( normType &W ){
    return W = ( maxNorm == W ) ? noNorm : normType(W+1);
  }

  extern const std::string DecayName[][2];
  extern const std::string OrdeningName[][2];
  extern const std::string WeightName[][2];
  extern const std::string MetricName[][2];
  extern const std::string InputFormatName[][2];
  extern const std::string AlgorithmName[][2];
  extern const std::string SmoothingName[][2];
  extern const std::string VerbosityName[][2];
  extern const std::string NormalisationName[][2];

  WeightType charToWeig( char );
  AlgorithmType charToAlg( char  );
  normType charToNorm( char  );

}

namespace TiCC {
  //
  // We create specializations of TiCC templates
  // the must be placed in the TiCC namespace (isn't it?)
  //
  using namespace Timbl;

  template <>
    inline DecayType stringTo<DecayType>( const std::string& str ) {
    DecayType d = UnknownDecay;
    for ( ++d; d < MaxDecay; ++d ){
      if ( compare_nocase( str, DecayName[d][0] ) ||
	   compare_nocase( str, DecayName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to decayType failed" ) );
  }

  template <>
    inline std::string toString<DecayType>( const DecayType& W, bool b ){
    if ( b ){
      return DecayName[W][1];
    }
    else {
      return DecayName[W][0];
    }
  }

  template <>
    inline OrdeningType stringTo<OrdeningType>( const std::string& str ) {
    OrdeningType d = UnknownOrdening;
    for ( ++d; d < MaxOrdening; ++d ){
      if ( compare_nocase( str, OrdeningName[d][0] ) ||
	   compare_nocase( str, OrdeningName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to ordeningType failed" ) );
  }

  template <>
    inline std::string toString<OrdeningType>( const OrdeningType& W,
					       bool b ){
    return OrdeningName[W][(b?1:0)];
  }

  template <>
    inline MetricType stringTo<MetricType>( const std::string& str ) {
    MetricType d = UnknownMetric;
    for ( ++d; d < MaxMetric; ++d ){
      if ( compare_nocase( str, MetricName[d][0] ) ||
	   compare_nocase( str, MetricName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to metricType failed" ) );
  }

  template <>
    inline std::string toString<MetricType>( const MetricType& W, bool b ){
    if ( b )
      return MetricName[W][1];
    else
      return MetricName[W][0];
  }

  template <>
    inline WeightType stringTo<WeightType>( const std::string& str ) {
    WeightType w = Unknown_w;
    if ( str.length() == 1 && isdigit(str[0]) ){
      w = charToWeig( str[0] );
    }
    if ( w != Unknown_w )
      return w;
    for ( ++w; w < Max_w; ++w ){
      if ( compare_nocase( str, WeightName[w][0] ) ||
	   compare_nocase( str, WeightName[w][1] ) ){
	return w;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to weightType failed" ) );
  }

  template <>
    inline std::string toString<WeightType>( const WeightType& W, bool b ){
    if ( b ){
      return WeightName[W][1];
    }
    else {
      return WeightName[W][0];
    }
  }

  template <>
    inline AlgorithmType stringTo<AlgorithmType>( const std::string& str ) {
    AlgorithmType a = Unknown_a;
    if ( str.length() == 1 && isdigit(str[0]) ){
      a = charToAlg( str[0] );
    }
    if ( a != Unknown_a )
      return a;
    for ( ++a; a < Max_a; ++a ){
      if ( compare_nocase( str, AlgorithmName[a][0] ) ||
	   compare_nocase( str, AlgorithmName[a][1] ) ){
	return a;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to algorithmType failed" ) );
  }

  template <>
    inline std::string toString<AlgorithmType>( const AlgorithmType& a,
						bool b ){
    if ( b )
      return AlgorithmName[a][1];
    else
      return AlgorithmName[a][0];
  }

  template <>
    inline InputFormatType stringTo<InputFormatType>( const std::string& str ){
    InputFormatType d = UnknownInputFormat;
    for ( ++d; d < MaxInputFormat; ++d ){
      if ( compare_nocase( str, InputFormatName[d][0] ) ||
	   compare_nocase( str, InputFormatName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to weightType failed" ) );
  }

  template <>
    inline std::string toString<InputFormatType>( const InputFormatType& i,
						  bool b ){
    if ( b ){
      return InputFormatName[i][1];
    }
    else {
      return InputFormatName[i][0];
    }
  }

  template <>
    inline SmoothingType stringTo<SmoothingType>( const std::string& str ) {
    SmoothingType d = UnknownSmoothing;
    for ( ++d; d < MaxSmoothing; ++d ){
      if ( compare_nocase( str, SmoothingName[d][0] ) ||
	   compare_nocase( str, SmoothingName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to smoothingType failed" ) );
  }

  template <>
    inline std::string toString<SmoothingType>( const SmoothingType& s,
						bool b ){
    if ( b ){
      return SmoothingName[s][1];
    }
    else {
      return SmoothingName[s][0];
    }
  }

  template <>
    inline normType stringTo<normType>( const std::string& str ) {
    normType d = unknownNorm;
    if ( str.length() == 1 && isdigit(str[0]) ){
      d = charToNorm( str[0] );
    }
    if ( d != unknownNorm )
      return d;
    for ( ++d; d < maxNorm; ++d ){
      if ( compare_nocase( str, NormalisationName[d][0] ) ||
	   compare_nocase( str, NormalisationName[d][1] ) ){
	return d;
      }
    }
    throw( std::runtime_error( "conversion from string '"
			       + str + "' to normalisationType failed" ) );
  }

  template <>
    inline std::string toString<normType>( const normType& s,
					   bool b ){
    if ( b ){
      return NormalisationName[s][1];
    }
    else {
      return NormalisationName[s][0];
    }
  }

  inline bool string_to_verbflag( const std::string& line,
				  VerbosityFlags &a ){
    unsigned int i;
    for ( i=0; VerbosityName[i][0][0] != '\0'; i++ )
      if ( compare_nocase( line, VerbosityName[i][0] ) ||
	   compare_nocase( line, VerbosityName[i][1] ) ){
	if ( i==0 ){
	  a = NO_VERB;
	}
	else{
	  a = (VerbosityFlags)(1<<(i-1));
	}
	return true;
      }
    return false;
  }

  template <>
    inline VerbosityFlags stringTo<VerbosityFlags>( const std::string& str ) {
    std::vector<std::string> tmp;
    size_t cnt = TiCC::split_at( str, tmp, "+" );
    VerbosityFlags V = NO_VERB;
    for ( size_t i=0; i < cnt; ++i ){
      VerbosityFlags Flag;
      if ( string_to_verbflag( tmp[i], Flag ) ){
	V |= Flag;
      }
      else {
	throw( std::runtime_error( "conversion from string '"
				   + str + "' to verbosityFlag failed" ) );
      }
    }
    return V;
  }

  inline std::string verbosity_to_string( int v, bool full ){
    if ( v == 0 )
      return VerbosityName[0][(full?1:0)];
    else {
      std::string OutLine;
      bool first = true;
      for ( unsigned int i=1; VerbosityName[i][0][0] != '\0'; ++i )
	if ( v & (1<<(i-1)) ){
	  if (first)
	    first = false;
	  else
	    OutLine += '+';
	  OutLine += VerbosityName[i][(full?1:0)];
	}
      return OutLine;
    }
  }

  template <>
    inline std::string toString<VerbosityFlags>( const VerbosityFlags& v,
						 bool full ){
    return verbosity_to_string( (int)v, full );
  }

  inline std::string toString( const AlgorithmType& a, bool b=false ){
    return toString<AlgorithmType>( a, b );
  }

  inline std::string toString( const MetricType& a, bool b=false ){
    return toString<MetricType>( a, b );
  }

  inline std::string toString( const WeightType& a, bool b=false ){
    return toString<WeightType>( a, b );
  }

  inline std::string toString( const InputFormatType& a, bool b=false ){
    return toString<InputFormatType>( a, b );
  }

  inline std::string toString( const DecayType& a, bool b=false ){
    return toString<DecayType>( a, b );
  }

  inline std::string toString( const OrdeningType& a, bool b=false ){
    return toString<OrdeningType>( a, b );
  }

  inline std::string toString( const VerbosityFlags& a, bool b=false ){
    return toString<VerbosityFlags>( a, b );
  }

}
#endif
