/* TreeSize for Unix  http://treesize.sf.net/
 * Copyright (c) 2006-2007 Marcos Diez <marcos_AT_unitron.com.br>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */


#include <stdio.h>
#include <sys/statvfs.h>

#include "FIfolderInfo.h"
#include "FAfolderAnalyzer.h"
#include "TSmonitor.h"
/* int snprintf(char *str, size_t size, const char *format, ...); */
/* char *strdup(const char *s); */

static BTkey BTkeyDup( const BTkey sourceKey ){
  return sourceKey;


}

static int BTkeyCmp(  const BTkey key1 , const BTkey key2 ){
  if( key1 == key2 ) return 0;
  if( key1 > key2 ) return 1;
  return -1;
}

static void BTfreeKey( BTkey key ){
  /* do nothing ! */
}



static DevInfo
newDevInfo( dev_t st_dev ,
	    FolderInfo rootFolder ){

  DevInfo thisDevInfo;
  myMalloc( thisDevInfo );
  thisDevInfo->st_dev = st_dev;
  thisDevInfo->rootFolder= rootFolder;
  thisDevInfo->equalFiles = BTnewTree( BTkeyDup , BTkeyCmp , BTfreeKey , (void (*)(void*)) BTfreeKey );
  thisDevInfo->freeSpace = FIgetDirFreeSpace( "." );
  return thisDevInfo;
}
	    
void FIfree( FolderInfo thisFolder ){
/*   printf("FREE: %s\n" , thisFolder->fullPath ); */
  if( !thisFolder ) return;
  if( ! FIisFileFolder( thisFolder ) ) free( thisFolder->fullPath );
  if( FIisMountPoint( thisFolder ) ){
    BTdelTree( thisFolder->device->equalFiles );
    free( thisFolder->device );
  }
  LLdelWholeList( thisFolder->subFolders ,  (void (*)(void *)) FIfree );

  free( thisFolder );
}

static char strFILES[] = "[Files]";
void FIaddFileFolder( FolderInfo theFolder  ){
  /* first we allocate the folder */
  FolderInfo fileFolder = FInew( strFILES , 0 , 1 , theFolder );

  /* now we ajust */
  fileFolder->fileFolder=1;
  fileFolder->totalLogicalSize =
    fileFolder->filesLogicalSize = theFolder->filesLogicalSize;
  fileFolder->totalPhysicalSize =
    fileFolder->filesPhysicalSize = theFolder->filesPhysicalSize;
  /* finally, we add it! */
  LLaddElement( theFolder->subFolders , fileFolder );
}


fileSizeUnit FIgetDirFreeSpace( char *directory ){
  struct statvfs statVfs;
  if( -1 == statvfs( directory , &statVfs )){
    perror( directory );
    return 0;
  }
  return statVfs.f_bsize * statVfs.f_bavail ;
}

static char *simpleFolderName( char *fullName ){
  char *name = strrchr( fullName , '/' );
/*   printf("%s %s\n" , fullName , fullName ); */
  return name ? name +1 : fullName;
}

FolderInfo FInew( char *fullName , struct stat *dirStat  , bool accessible , FolderInfo father ){
  FolderInfo thisFolder;
  myMalloc( thisFolder );

/*   printf("%s [%s]\n" , __FUNCTION__ , fullName ); */
  /* easy */
  thisFolder->fullPath=fullName;

  thisFolder->father=father;
  thisFolder->accessible=accessible;
  thisFolder->fileFolder=0;
  /* not yet known */
  
  thisFolder->totalLogicalSize =
    thisFolder->totalPhysicalSize =
    thisFolder->filesPhysicalSize =
    thisFolder->filesLogicalSize =0;
/*   thisFolder->containUnaccessibleFiles=0; */

  thisFolder->subFolders=LLnewList();

  /* we have the following cases:
     
  a) root folder        (no father)
  b) new mount point    (we will refuse the father)
  c) file folder        (has father, but the st_dev is zero)
  d) normal folder      enheirets everything from the father

  */
  
  if( !father ){ /* root folder */
    thisFolder->device=newDevInfo( dirStat->st_dev , thisFolder );
    thisFolder->name=fullName;
/*     printf("%s %lld\n"  , thisFolder->name, FIgetFreeSpace( thisFolder ) ); */
  }else{
    if( !dirStat  ){	/* fileFolder */
      thisFolder->device=father->device;
      thisFolder->fullPath=father->fullPath;
      thisFolder->name=fullName;
    }else
      if( dirStat->st_dev != father->device->st_dev ){	/* new mount point */
	thisFolder->father=0;
	thisFolder->device=newDevInfo( dirStat->st_dev , thisFolder );
	thisFolder->name=simpleFolderName( fullName );


      }else{			/* plain normal folder */
	thisFolder->device=father->device;
	thisFolder->name=simpleFolderName( fullName );
      }
    }
  
  return thisFolder;
}

void FIaddFile( FolderInfo theFolder , char *fileName , struct stat *fileStat ){
  fileSizeUnit filePhysicalSize;

  /* we always add the logical Size */
  theFolder->totalLogicalSize+= fileStat->st_size;
  theFolder->filesLogicalSize+= fileStat->st_size;

  
  /* but we just add the PhysicalSize if it was NOT yet been added */
  if( fileStat->st_nlink > 1 ){
    /* the file existst at least somewhere else */
    int status = BTadd( theFolder->device->equalFiles , (BTkey) fileStat->st_ino , theFolder->name  );
    if( status == BT_KEY_ALREADY_EXISTS ){
      /* it already exists... lets print it just for the heck of it */
      printf("File already exists: %s/%s\n" , theFolder->name , fileName );
      return;
    }
  }
  

  filePhysicalSize= fileStat->st_blocks * 512;  /* man 2 stat explains why not fileStat->st_blksize */
  
  theFolder->totalPhysicalSize+= filePhysicalSize;
  theFolder->filesPhysicalSize+= filePhysicalSize;

/*   printf("%10ld\t%10ld\t%10ld\t%s\n",  filePhysicalSize , theFolder->totalPhysicalSize , theFolder->filesPhysicalSize ,  fileName ); */
}

void FIaddFolder( FolderInfo theFolder, char *newFolderName, struct stat *newFolderStat , bool onlyOneFileSystem ){
  FolderInfo newFolder;
  FAanalyzeRec( newFolderName , newFolderStat , theFolder , &newFolder , onlyOneFileSystem  );
  if( !TSrunning ) return;
/*   printf(" %s %s\n" , __FUNCTION__ , newFolderName ); */
  if( newFolder->father ){
    theFolder->totalLogicalSize+=  newFolder->totalLogicalSize;
    theFolder->totalPhysicalSize+= newFolder->totalPhysicalSize;
  }
  LLaddElement( theFolder->subFolders , newFolder );
}


void FIprint( FolderInfo theFolder ){
  int numKids = LLlistSize( theFolder->subFolders );
    
  printf("FOLDER: %s\n"
	 "Device: 0x%x\n" , theFolder->name , (unsigned int)  theFolder->device->st_dev );

  printf("TLS: %lld\tTPS: %lld\tFLS: %lld\tFPS: %lld\n" ,
	 theFolder->totalLogicalSize ,
	 theFolder->totalPhysicalSize,
	 theFolder->filesLogicalSize,
	 theFolder->filesPhysicalSize );

  printf("Father: %s\nRoot:   %s\n"  , theFolder->father ? theFolder->father->name : 0  , theFolder->device->rootFolder->name );

  printf("MountPoint: %d Accessiblefolder: %d Kids %d\n",
	 FIisMountPoint( theFolder ) ,
	 theFolder->accessible , numKids  );
  {
    FolderInfo *folders = LLlist2Array(theFolder->subFolders);
    if( folders ){
      while(*folders){
	/*       printf("[%p " , folders ); fflush( 0 ); */
	/*       printf("%p]\n" , *folders ); fflush( 0 ); */
	
	printf("%s " , (*folders)->name  );
	
	folders++;
      }
    }
  }
    putchar( '\n' );

}
