// ---------------------------------------------------------------------
//   DiskUsage.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/>.
//
// ---------------------------------------------------------------------

/** 
 * This macro determines which file system interface shall be used, one
 * replacing the other. `_FILE_OFFSET_BITS' allows the 64 bit interface
 * to replace the old interface.
 *
 * If `_FILE_OFFSET_BITS' is undefined, or if it is defined to the value
 * `32', nothing changes. The 32 bit interface is used and types like
 * `off_t' have a size of 32 bits on 32 bit systems.
 *
 * On 32bit systems 'stat -> stat64' and 'lstat -> lstat64'
 * transparently.
*/
#define _FILE_OFFSET_BITS 64

#ifndef __MINGW32__
#define HAVE_LSTAT
#endif

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

#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <cstring> // for strerror
#include <errno.h> // for errno

#include <string>
#include <iostream>

using namespace std;

//----------------------------------------------------------------------
unsigned DiskUsage::m_block_size;

//----------------------------------------------------------------------
Params::UBigInt 
DiskUsage::GetSize( const char* filename ) 
{
   struct stat st;

#ifdef HAVE_LSTAT
   if( lstat( filename, &st ) != 0 )
#else
   if( stat( filename, &st ) != 0 )
#endif
   {
      cerr << "> Could not read: " << filename << endl;
      return 0;
   }
      
   if( S_ISDIR( st.st_mode ) )
   {
      char saved_path[m_max_path_name], tmp_path[m_max_path_name];

      // saves current path before chdir to 'filename' directory
      if( getcwd( saved_path, m_max_path_name - 1 ) == NULL )
      {
         cerr << "> Could not get current dir: " << strerror( errno ) << endl;
         return 0;
      }

      if( chdir( filename ) != 0 ) 
      {
         cerr << "> Could not chdir to: " << filename << endl;
         return 0;
      }

      if( getcwd( tmp_path, m_max_path_name - 1 ) == NULL )
      {
         cerr << "> Could not get current dir: " << strerror( errno ) << endl;
         return 0;
      }

#ifdef HAVE_LSTAT
      if( lstat( tmp_path, &st ) != 0 )
#else
      if( stat( tmp_path, &st ) != 0 )
#endif
      {
         cerr << "> Could not read dir: " << tmp_path << endl;
         return 0;
      }

      Params::UBigInt total = 
                AllocationSize( static_cast<Params::UBigInt>( st.st_size ) ) 
                                         + DepthFirstTraversal( tmp_path );

      // return to the original path (important!)
      chdir( saved_path );

      return total;
   }
   else // ISREG, ISLNK, ISFIFO, ISBLK, ...
      return AllocationSize( static_cast<Params::UBigInt>( st.st_size ) );
}

//----------------------------------------------------------------------
Params::UBigInt
DiskUsage::AllocationSize( Params::UBigInt size ) 
{
   Params::UBigInt remainder = size % m_block_size;

   /* Test whether the filesize is smaller than the allocation block. 
    Also, 0-sized files occupy space equal to the allocation block size.  */
   if( remainder || size==0 ) 
      return size += m_block_size - remainder;
   
   // the filesize matches perfectly the allocation block
   return size;
}

//----------------------------------------------------------------------
Params::UBigInt
DiskUsage::DepthFirstTraversal( const char* currdir ) 
{
   if( chdir( currdir ) != 0 )
   {
      cerr << "> Could not chdir to: " << currdir << endl;
      return 0;
   }

   Params::UBigInt sum = 0;

   // open the directory
   DIR *dir = opendir( "." );
   if( dir == NULL )
   {
      cerr << "> Could not open dir: " << currdir << endl;
      chdir( ".." );

      return 0;
   }

   struct dirent *de;
   while( ( de = readdir( dir ) ) )
   {
      struct stat st;
#ifdef HAVE_LSTAT
      if( lstat( de->d_name, &st ) != 0 )
#else
      if( stat( de->d_name, &st ) != 0 )
#endif
      {
         cerr << "> Could not read: " << de->d_name << endl;
         continue;
      }

      if( S_ISDIR( st.st_mode ) ) // is a directory (= recursive)
      {
         string tmp_str = de->d_name;

         if( ( tmp_str != "." ) && ( tmp_str != ".." ) )
         {
            string filename = string( currdir ) + "/" + tmp_str;

            sum += AllocationSize( static_cast<Params::UBigInt>( st.st_size ) ) 
                                    + DepthFirstTraversal( filename.c_str() );
         }
      }
      else // ISREG, ISLNK, ISFIFO, ISBLK, ...
         sum += AllocationSize( static_cast<Params::UBigInt>( st.st_size ) );
   }
   closedir( dir );
   chdir( ".." );

   return sum;
}

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