// ---------------------------------------------------------------------
//   Input.cc (created on Tue Aug 23 01:08: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 "Input.h"

#include "DiskUsage.h"
#include "util/Exception.h"

using namespace std;

//----------------------------------------------------------------------
Input::Input( Params& params ): m_total_size( 0.0 ), m_params( params ) 
{
   DiskUsage::SetBlockSize(m_params.m_block_size);

   Initialize();
}

//----------------------------------------------------------------------
void 
Input::Initialize() 
{ 
   if (m_params.m_verbose) cout << 
      "> Reading from input... " << flush << endl;

   // bypass file reading?
   if (m_params.m_direct_input) { ReadInput(); } else { LoadFiles(); }

   // Check whether there are still files/items to process or not.
   if (m_files.size() == 0) throw E_NoInputFiles();
}

//----------------------------------------------------------------------
void
Input::ReadInput()
{
   //--- read data from stdin (pipe)
   if (m_params.m_pipe) 
   {
      /* If the option --null-data (-z) was specified, then the end of a
       * filename is determined by the NULL (\0) char; otherwise, the
       * newline (\n) is used. This option is very useful, for intance,
       * when dealing with output from 'find' using the 'print0' option.
       *
       * For input files given as args (command-line), this option has
       * no effect. */
      const char delim = m_params.m_null_data ? '\0' : '\n';

      string s;
      getline(cin, s, delim);

      if (cin) 
      {
         do 
         {
            StringToSizeID(s); //try to add the item into m_files
            getline(cin, s, delim);

         }  while (cin);
      }
   }

   // --- read data from args too (if any)
   while (m_params.m_cmdline_items.size()>0)
   {
      StringToSizeID(m_params.m_cmdline_items.front());
      m_params.m_cmdline_items.pop_front(); // release memory
   }
}

//----------------------------------------------------------------------
void
Input::StringToSizeID( const string& s )
{
   // just a empty string, ignoring it
   if (s == "") return;

   /* Column separator (between 'size' and 'identifier'). Only *one* of
    * these characters is allowed: whitespace (' ') or tabulation ('\t').
    * If there is given more than one of the above, then the extra chars
    * will be part of the ID string. This is particularly useful when
    * dealing with filenames with unusual chars (like newline) and the
    * option '-z' is specified; thus, it is known that any char after
    * the separator (up to '\0') is part of the filename.
    */
   const char* delimiters = "\t ";

   // find the first digit of the 'size' column
   string::size_type size_begin = s.find_first_not_of(delimiters);
   if (size_begin == string::npos)
   {
      cerr << "> [Ignoring] Could not get size of: " << s << endl;

      return;
   }

   // find the first 'tab' or 'space' after the 'size' column
   string::size_type size_end = s.find_first_of(delimiters, size_begin);

   Params::Size_t size;

   // converts the 'size' column into 'size' variable
   if( !Params::StringToDouble( size, string( s, size_begin, size_end ) ) )
   {
      cerr << "> [Ignoring] Could not get size of: " << s << endl;

      return;
   }

   /* When an ID is not given, then 'size_end == string::npos'. In this case, 
    * the ID will be a string whose content = size. For example, given
    * the entry <100>, the size will be 100 and the ID will be "100".
    *
    * If there was given a pair "x " (note the space/tab), then the ID will
    * be a string of size 0 (null string). For intance, given <100 >,
    * the size will be 100 and the ID will be "".
    *
    * If none of the above, the string ID will contain all chars from
    * 'size_end + 1' position to the end of 's'. For example, given the
    * pair <100 a hundred>, the size will be 100 while the ID will be
    * "a hundred".
    * */
   string str_id( s, size_end == string::npos ? size_begin : size_end + 1, 
         string::npos );
         
   // size calculation considering the allocation block
   Params::Size_t total_size = (m_params.m_no_metric) ? size : 
      DiskUsage::AllocationSize( static_cast<Params::Size_t>( size * 
               m_params.DI_Factor() ) );

   if (CheckRange(str_id, total_size)) 
   {
      m_files.push_back(new SizeName(str_id, total_size));
      m_total_size += total_size;
   }
}

//----------------------------------------------------------------------
void
Input::LoadFiles()
{
   //--- read data from stdin (pipe)
   if (m_params.m_pipe) 
   {
      /* If the option --null-data (-z) was specified, then the end of a
       * filename is determined by the NULL (\0) char; otherwise, the
       * newline (\n) is used. This option is very useful, for intance,
       * when dealing with output from 'find' using the 'print0' option.
       *
       * For input files given as args (command-line), this option has
       * no effect. */
      const char delim = m_params.m_null_data ? '\0' : '\n';

      string s;
      getline(cin, s, delim);

      if (cin) 
      {
         do 
         {
            AddFile(s); //try to add the given file in m_files
            getline(cin, s, delim);

         }  while (cin);
      }
   }

   // --- read data from args too (if any)
   while (m_params.m_cmdline_items.size()>0)
   {
      AddFile(m_params.m_cmdline_items.front());

      m_params.m_cmdline_items.pop_front(); // release memory
   }
}

//---------------------------------------------------------------------
void
Input::AddFile(const string& s)
{
   Params::Size_t size = DiskUsage::GetSize(s.c_str());
   if (size == 0) { cerr << "> [Ignoring] file/dir: " << s << endl; return; }

   if (CheckRange(s,size)) 
   {
      m_files.push_back(new SizeName(s, size)); 
      m_total_size += size;
   }
}

//---------------------------------------------------------------------
bool
Input::CheckRange(const string& s, Params::Size_t size) const
{
   if (size > m_params.m_target)
   {
      cerr << "> [Ignoring] bigger than 'target'(" 
           << m_params.PrettySize(m_params.m_target) << "): " << s
           << " (" << m_params.PrettySize(size) << ")" << endl;

      return false;
   }

   if (size > m_params.m_max_size)
   {
      cerr << "> [Ignoring] bigger than '--max-size'(" 
           << m_params.PrettySize(m_params.m_max_size) << "): " 
           << s << " (" << m_params.PrettySize(size) << ")" << endl;

      return false;
   }

   if (size <= 0.0)
   {
      cerr << "> [Ignoring] 0-sized input: " << s << endl;

      return false;
   } 

   if (size < m_params.m_min_size)
   {
      cerr << "> [Ignoring] smaller than '--min-size'(" 
           << m_params.PrettySize(m_params.m_min_size) << "): " << s
           << " (" << m_params.PrettySize(size) << ")" << endl;

      return false;
   } 

   return true;
}

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