// ---------------------------------------------------------------------
//   Params.cc (created on Tue Aug 23 18:48:35 BRT 2005)
// 
//   Genetic Algorithm File Fitter (gaffitter)
//
//   Copyright (C) 2005-2015 Douglas A. Augusto
// 
// This file is part of gaffitter.
// 
// gaffitter 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.
// 
// gaffitter 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 gaffitter; if not, see <http://www.gnu.org/licenses/>.
//
// ---------------------------------------------------------------------

#include "Params.h"
#include "util/Exception.h"
#include "util/CmdLineParser.h"

#include <sstream>
#include <cmath>

using namespace std;

//---------------------------------------------------------------------
void
Params::ShowVersion() const // --version
{
   cout

   << "gaffitter 1.0.0 (Genetic Algorithm File Fitter) (C) 2005-2015\n"
   << "\n"
   << "This is free software. You may redistribute copies of it under the terms\n"
   << "of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
   << "There is NO WARRANTY, to the extent permitted by law.\n"
   << "\n"
   << "Written by Douglas A. Augusto (daaugusto).\n";
}

//---------------------------------------------------------------------
void
Params::ShowUsage(const char* app = "fit") const // -h or --help
{
   cout  

   << "Genetic Algorithm File Fitter (GAFFitter)\n"
   << "\n"
   << "Usage: " << app << " -t target[unit] [options...] <files>\n"
   << "       ... | " << app << " - -t target[unit] [options...] [files]\n"
   << "\n"
   << "  the unit suffixes 'k', 'm', 'g' or 't' can be used, where:\n"
   << "     k = KB/KiB, m = MB/MiB, g = GB/GiB and t = TB/TiB [default = bytes]\n"
   << "\n"    
   << "General options:\n"
   << "  -t <f>[unit], --target <f>[unit]\n"
   << "     target size (mandatory), f>0.0\n"
   << "  --si\n"
   << "     use powers of 1000 (not 1024) for target, min, max and output sizes\n"
   << "  --bins <n>, --vols <n>\n"
   << "     maximum number of bins (volumes) [default = \"unlimited\"]\n"
   << "  -v, --verbose\n"
   << "     verbose\n"
   << "  --min <f>[unit], --min-size <f>[unit]\n"
   << "     minimum file size [default = none]\n"
   << "  --max <f>[unit], --max-size <f>[unit]\n"
   << "     maximum file size [default = none]\n"
   << "  -B <n>, --block-size <n>\n"
   << "     the smallest amount of bytes a file can occupy [default = 1]\n"
   << "  --ss, --show-size\n"
   << "     print the size of each file\n"
   << "  --sb, --show-bytes\n"
   << "     also print the sizes in bytes\n"
   << "  --hi, --hide-items\n"
   << "     don't print the selected items\n"
   << "  --hs, --hide-summary\n"
   << "     hide summary line containing sum, difference and number of\n"
   << "     selected items\n"
   << "  -s, --sort-by-size\n"
   << "     sort the output by size, not by name\n"
   << "  -n, --no-case\n"
   << "     use case-insensitive sorting\n"
   << "  -r, --sort-reverse\n"
   << "     sort the output in reverse order\n"
   << "  -z, --null-data\n"
   << "     assume NULL (\\0) as the delimiter of input files via stdin (pipe)\n"
   << "  -Z, --null\n"
   << "     same as --dw '\\0'. See also the -0 and --hs options\n"
   << "  -0, --null-bins\n"
   << "     same as --bs '\\0'. See also the -Z and --hs options\n"
   << "  --bs <char>, --bins-separator <char>\n"
   << "     separate bins (vols) with \"char\" [default = newline]\n"
   << "  --ew <char>, --enclose-with <char>\n"
   << "     enclose file names with \"char\" [default = none]\n"
   << "  --dw <char>, --delimit-with <char>\n"
   << "     delimit file names (lines) with \"char\" [default = newline]\n"
   << "  -1 (or --fast) ... -9 (or --best)\n"
   << "     select preset search parameters [default = -3]\n"
   << "  --version\n"
   << "     print GAFFitter version and exit\n"
   << "  -h, --help\n"
   << "     print this help and exit\n"
   << "\n"
   << "Direct Input options:\n"
   << "  --di, --direct-input\n"
   << "     switch to direct input mode, i.e., read directly \"size identifier\"\n"
   << "     pairs instead of file names\n"
   << "  --di-b, --di-bytes\n"
   << "     assume input sizes as bytes\n"
   << "  --di-k, --di-kb\n"
   << "     assume input sizes as kibi bytes (KiB); KB if --di-si\n"
   << "  --di-m, --di-mb\n"
   << "     assume input sizes as mebi bytes (MiB); MB if --di-si\n"
   << "  --di-g, --di-gb\n"
   << "     assume input sizes as gibi bytes (GiB); GB if --di-si\n"
   << "  --di-t, --di-tb\n"
   << "     assume input sizes as tebi bytes (TiB); TB if --di-si\n"
   << "  --di-si\n"
   << "     use powers of 1000 (not 1024) for input sizes\n"
   << "\n"
   << "Genetic Algorithm options:\n"
   << "  --ga-s <n>, --ga-seed <n>\n"
   << "     GA initialization seed, n>=0 [default = 1]; 0 = random\n"
   << "  --ga-rs, --ga-random-seed\n"
   << "     use random GA seed (same as --ga-seed 0)\n"
   << "  --ga-ng <n>, --ga-num-generations <n>\n"
   << "     maximum number of generations, n>0 [default = auto]\n"
   << "  --ga-ps <n>, --ga-pop-size <n>\n"
   << "     number of individuals, n>tournament_size [default = auto]\n"
   << "  --ga-cp <f>, --ga-cross-prob <f>\n"
   << "     crossover probability, 0.0<=f<=1.0 [default = 0.75]\n"
   << "  --ga-mp <f>, --ga-mutation-prob <f>\n"
   << "     mutation probability, 0.0<=f<=1.0 [default = 0.30]\n"
   << "  --ga-sp <n>, --ga-sel-pressure <n>\n"
   << "     selection pressure (tournament size), 2<=n<pop_size [default = 2]\n"
   << "  --ga-theo [n], --ga-theoretical [n]\n"
   << "     stop if the theoretical minimum number of bins is reached. If n is\n"
   << "     given, it is assumed to be the theoretical minimum number of bins.\n"
   << "\n"
   << "Other search methods\n"
   << "  --ap, --approximate\n"
   << "     local approximation using Best Fit search (non-optimal but\n"
   << "     very fast)\n"
   << "  --sp, --split\n"
   << "     just split the input when target size is reached (preserves\n"
   << "     original order while splitting)\n";
#if 0
   << "  --bf, --brute-force\n"
   << "     tries all possible combinations (use carefully!)\n"
#endif
}

//----------------------------------------------------------------------
bool 
Params::Initialize()
{
   // The 'O' object holds and processes user's input arguments
   CmdLine::Parser O( m_argc, m_argv,
         CmdLine::SILENT | CmdLine::OUT_OF_RANGE | CmdLine::NO_VALUE );

   // Let's declare all possible command-line options

   O.Bool.Add("-h","--help");
   O.Bool.Add("--version");

   O.Bool.Add("-");

   O.Bool.Add("--si");

   O.Bool.Add("--di","--direct-input");
   O.Bool.Add("--di-b","--di-bytes");
   O.Bool.Add("--di-k","--di-kb");
   O.Bool.Add("--di-m","--di-mb");
   O.Bool.Add("--di-g","--di-gb");
   O.Bool.Add("--di-t","--di-tb");
   O.Bool.Add("--di-si");

   O.String.Add( "-t","--target" );

   O.Int.Add("--vols","--bins",numeric_limits<int>::max(),1,
                numeric_limits<int>::max());

   O.String.Add( "--min", "--min-size", "" );
   O.String.Add( "--max", "--max-size", "" );

   O.Int.Add("-B","--block-size",1,1,numeric_limits<int>::max());

   O.Bool.Add("-v","--verbose");

   O.Bool.Add("--hi","--hide-items");
   O.Bool.Add("--hs","--hide-summary");
   O.Bool.Add("--ss","--show-size");
   O.Bool.Add("--sb","--show-bytes");

   O.Bool.Add("-s","--sort-by-size");
   O.Bool.Add("-r","--sort-reverse");
   O.Bool.Add("-n","--no-case");

   O.Int.Add("--ga-ng","--ga-num-generations",0,0,numeric_limits<int>::max());
   O.Int.Add("--ga-ps","--ga-pop-size",0,0,numeric_limits<int>::max());
   O.Float.Add("--ga-cp","--ga-cross-prob",0.75,0.0,1.0);
   O.Float.Add("--ga-mp","--ga-mutation-prob",0.30,0.0,1.0);
   O.Int.Add("--ga-sp","--ga-sel-pressure",0,0,numeric_limits<int>::max());
   O.Bool.Add("--ga-rs","--ga-random-seed");
   O.Int.Add("--ga-s","--ga-seed",1,0,numeric_limits<long>::max());
   O.Bool.Add( "--ga-theo","--ga-theoretical" );
   O.Int.Add( "--ga-theo","--ga-theoretical", 0, 1, 
         numeric_limits<int>::max() ).UnSet( CmdLine::NO_VALUE );

#if 0
   O.Bool.Add("--bf","--brute-force");
#endif
   O.Bool.Add("--ap","--approximate");
   O.Bool.Add("--sp","--split");

   O.Bool.Add("-z","--null-data");
   O.Bool.Add("-Z","--null");
   O.Bool.Add("-0","--null-bins");
   O.Char.Add("--dw","--delimit-with",'\n');
   O.Char.Add("--ew","--enclose-with",'\0');
   O.Char.Add("--bs","--bins-separator",'\n');

   // Presets
   O.Bool.Add("-1","--fast");
   O.Bool.Add("-2");
   O.Bool.Add("-3");
   O.Bool.Add("-4");
   O.Bool.Add("-5");
   O.Bool.Add("-6");
   O.Bool.Add("-7");
   O.Bool.Add("-8");
   O.Bool.Add("-9","--best");

   // -- Get the options! ----------------
   /* Right now, the 'O' object will process the command-line, i.e.,
    * it will try to recognize the options and their respective arguments. */
   O.Process( m_cmdline_items );
   // ------------------------------------

   if (O.Bool.Get("-h")) { ShowUsage(); return false; /* exit */ }
   if (O.Bool.Get("--version")) { ShowVersion(); return false; /* exit */ }

   // input via PIPE: ... | fit - ...
   m_pipe = O.Bool.Get("-");

   // ---- units
   if (O.Bool.Get("--si"))
   {
      m_unit_symbol='\0';
      m_unit_power=1000.0;
   }
   else
   {
      m_unit_symbol='i';
      m_unit_power=1024.0;
   }

   // --- manual input (no files)
   m_direct_input = O.Bool.Get("--di");

   if (m_direct_input)
   {
      double di_power = O.Bool.Get("--di-si") ? 1000.0 : 1024.0;

      // assume input sizes Bytes, KB, MB or GB
      if (O.Bool.Get("--di-b")) m_di_factor = 1.0;
      else if (O.Bool.Get("--di-k")) m_di_factor = KB(di_power);
      else if (O.Bool.Get("--di-m")) m_di_factor = MB(di_power);
      else if (O.Bool.Get("--di-g")) m_di_factor = GB(di_power);
      else if (O.Bool.Get("--di-t")) m_di_factor = TB(di_power);
      else m_no_metric = true; // default to no metric!
   }

   // ---- Target size (default = none) [mandatory option]
   // Bytes, KB, MB or GB for target, min and max size
   if( !GetSize( O.String.Get( "-t" ), m_target ) || m_target <= 0.0 )
      throw E_NoTarget(); 

   // --- min and max file size
   if( !GetSize( O.String.Get( "--min" ), m_min_size ) || m_min_size <= 0.0 )
      m_min_size = 0.0;
   if( !GetSize( O.String.Get( "--max" ), m_max_size ) || m_max_size <= 0.0 )
      m_max_size = numeric_limits<Size_t>::max();

   /* if there is a metric (B, KB, etc.) then 'target' needs to be
      rounded, because it actually represents bytes. */
   if( !m_no_metric ) 
   {
      m_target = floor( m_target );
      m_min_size = ceil( m_min_size );
      m_max_size = floor( m_max_size );
   }

   // ---- Iterations (default = as much as possible)
   m_max_bins = O.Int.Get( "--bins" );

   // --- block size
   m_block_size = O.Int.Get("-B");

   // --- verbose
   m_verbose = O.Bool.Get("-v");

   // --- hide items (the list of items/files)?
   m_hide_items = O.Bool.Get("--hi");

   // --- hide summary (last line)?
   m_hide_summary = O.Bool.Get("--hs");
   
   // --- show sizes?
   m_show_size = O.Bool.Get("--ss");
   
   // --- show bytes?
   // showing bytes in direct input mode (with no metric) is no sense.
   m_show_bytes = O.Bool.Get("--sb") && !m_no_metric;

   // --- sort by size?
   m_sort_by_size = O.Bool.Get("-s");

   // --- reverse order while sorting?
   m_sort_reverse = O.Bool.Get("-r");

   // --- case-sensitive or insensitive? (only for "sort by name")
   m_no_case = O.Bool.Get("-n");

   // ---- Genetic Algorithm ------------------------------------------
   /* m_ga_pop_size and m_ga_num_gens are (auto)adjusted in
    * Optimizer::Initialize depending on the input size */

   // -- Presets
   if (O.Bool.Get("-9")) {
      m_ga_psf = 95; m_ga_ngf = 85; m_ga_cross_prob = 0.95; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-8")) {
      m_ga_psf = 90; m_ga_ngf = 55; m_ga_cross_prob = 0.80; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-7")) {
      m_ga_psf = 95; m_ga_ngf = 45; m_ga_cross_prob = 0.75; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-6")) {
      m_ga_psf = 60; m_ga_ngf = 40; m_ga_cross_prob = 0.75; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-5")) {
      m_ga_psf = 55; m_ga_ngf = 40; m_ga_cross_prob = 0.80; m_ga_mut_prob = 0.45;
   }
   else if (O.Bool.Get("-4")) {
      m_ga_psf = 50; m_ga_ngf = 35; m_ga_cross_prob = 0.85; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-3")) {
      m_ga_psf = 30; m_ga_ngf = 40; m_ga_cross_prob = 0.75; m_ga_mut_prob = 0.30;
   }
   else if (O.Bool.Get("-2")) {
      m_ga_psf = 10; m_ga_ngf = 75; m_ga_cross_prob = 1.00; m_ga_mut_prob = 0.50;
   }
   else if (O.Bool.Get("-1")) {
      m_ga_psf = 5; m_ga_ngf = 65; m_ga_cross_prob = 0.90; m_ga_mut_prob = 0.50;
   }
   else {
      // Default preset's values (default preset = -3)
      m_ga_psf = 30; m_ga_ngf = 40; m_ga_cross_prob = 0.75; m_ga_mut_prob = 0.30;
   }

   // -- Number of generations
   m_ga_num_gens = O.Int.Get("--ga-ng"); // check range: done in 
                                            // Optimizer::Initialize
   // -- Population size
   m_ga_pop_size = O.Int.Get("--ga-ps"); // check range: done in 
                                            // Optimizer::Initialize

   // -- Crossover probability (the user may override preset values)
   if (O.Float.Found("--ga-cp")) m_ga_cross_prob = O.Float.Get("--ga-cp");

   // -- Mutation probability (the user may override preset values)
   if (O.Float.Found("--ga-mp")) m_ga_mut_prob = O.Float.Get("--ga-mp");

   // -- Selection pressure (currently "tournament size")
   m_ga_sel_pressure = O.Int.Get("--ga-sp"); // check range: done in 
                                                // Optimizer::Initialize

   // -- Initialization seed
   if (O.Bool.Get("--ga-rs")) m_ga_seed = 0;
   else m_ga_seed = O.Int.Get("--ga-s");

   // -- stop the search when the theoretical minimum number of bins is
   // reached?
   m_bins_theo = O.Int.Get( "--ga-theo" );
   m_theoretical = O.Bool.Get( "--ga-theo" ) || m_bins_theo > 0;

   // ---- Search ----------------------------------------------
#if 0
   // --- Brute Force Search. May be very slow ( O(2^|input|) )
   m_brute_force = O.Bool.Get("--bf");
#endif

   // --- Best First Search (very fast but just approximate)
   m_approximate = O.Bool.Get("--ap");

   // --- Split "Search"
   m_split = O.Bool.Get("--sp");

   // --------------------

   // Delimit and Enclose options
   m_null_data = O.Bool.Get( "-z" );

   if( O.Bool.Get( "-Z" ) )
      m_delimit_chr = '\0';
   else
      m_delimit_chr = O.Char.Get( "--dw" ); // default = "\n" (new line)

   m_enclose = O.Char.Get( "--ew", m_enclose_chr );

   if( O.Bool.Get( "-0" ) )
      m_bins_separator = '\0';
   else
      m_bins_separator = O.Char.Get( "--bs" );

   // ---------------
   return true;
}

//---------------------------------------------------------------------
std::string
Params::PrettySize(Size_t size) const
{
   std::ostringstream o;

   if (m_no_metric) // = (m_direct_input && m_no_metric)
   {
      // just print the size without units
      o.precision(16); o << size; return o.str();
   }

   // format adjust
   o << std::fixed; 
   
   // module
   Size_t positive_bytes = (size < 0.0) ? -size : size;

   o.precision(2);

   if (positive_bytes >= TB())
      o << size/GB() << 'T'<<m_unit_symbol<<'B';
   else if (positive_bytes >= GB())
      o << size/GB() << 'G'<<m_unit_symbol<<'B';
   else if (positive_bytes >= MB())
      o << size/MB() << 'M'<<m_unit_symbol<<'B';
   else if (positive_bytes >= KB())
      o << size/KB() << 'K'<<m_unit_symbol<<'B';
   else { o.precision(0); o << size << "Bytes"; }
   
   // print bytes together?
   if (m_show_bytes) { o.precision(0); o << " (" << size << ")"; }

   return o.str();
}
//---------------------------------------------------------------------
bool
Params::GetSize( const std::string& size_unit, Params::Size_t& value ) const
{
   if( size_unit.length() == 0 || !StringToDouble( value, size_unit ) ) 
      return false;

   int pos = size_unit.length() - 1;
   char unit = size_unit[pos];

   // Find the first alphabetic letter, so it is possible to write
   // -t 10m or -t 10mb (or -t 10M or -t 10MB)
   while( --pos > 0 && isalpha( size_unit[pos] ) ) { unit = size_unit[pos]; }

   if( isalpha( unit ) )
      switch( tolower( unit ) )
      {
         case 'k':
            return (value *= KB(), true);
         case 'm':
            return (value *= MB(), true);
         case 'g':
            return (value *= GB(), true);
         case 't':
            return (value *= TB(), true);
      }

   return true;
}

//---------------------------------------------------------------------
