/*    
   FllDfrg.c - code to fully optimize a drive.

   Copyright (C) 2002 Imre Leber

   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 2 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, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   If you have any questions, comments, suggestions, or fixes please
   email me at:  imre.leber@worldonline.be
*/

#include <string.h>
#include <stdio.h>

#include "fte.h"
#include "DfrgDrvr.h"
#include "FllDfrag\flldfrg.h"
#include "..\dtstruct\ClMovMap.h"
#include "..\..\modlgate\expected.h"
#include "defrmap.h"

/* Public functions */
static BOOL SelectFile(RDWRHandle handle, CLUSTER startfrom,
                       CLUSTER* clustertoplace);                         

/* Private functions */                  
static BOOL FileSelector(RDWRHandle handle, struct DirectoryPosition* pos,
                         void** structure);
static BOOL SlowSelectFile(RDWRHandle handle, CLUSTER startfrom,
                           CLUSTER* clustertoplace);  
                                         
struct Pipe
{
   CLUSTER cluster;
   CLUSTER clustertoplace;

   BOOL found;
};

BOOL FullyDefragmentVolume(RDWRHandle handle)
{
    BOOL result;
    
    if (CreateFastSelectMap(handle) == FAIL)
       return FALSE;
    
    result = DefragmentVolume(handle, SelectFile, FullDefragPlace);
    
    DestroyFastSelectMap();
    
    return result;
}

static BOOL SlowSelectFile(RDWRHandle handle, CLUSTER startfrom,
                           CLUSTER* clustertoplace)
{
    struct Pipe pipe, *ppipe = &pipe;

    pipe.cluster = startfrom;
    pipe.found   = FALSE;

    if (!WalkDirectoryTree(handle, FileSelector, (void**) &ppipe))
    {
       return FAIL;
    }
    
    if (pipe.found)
       *clustertoplace = pipe.clustertoplace;

    return pipe.found;
}

static BOOL FileSelector(RDWRHandle handle, struct DirectoryPosition* pos,
                         void** structure)
{
    BOOL isMovable;
    CLUSTER cluster;
    struct Pipe* pipe = *((struct Pipe**) structure);
    struct DirectoryEntry entry;

    if (!GetDirectory(handle, pos, &entry))
    {
       return FAIL;
    }

    /*
       Don't take LFN entries into consideration.
    */
    if (IsLFNEntry(&entry)) return TRUE;

    /*
       Be carefull for hidden or system files.
    */
    if ((entry.attribute & FA_HIDDEN) || (entry.attribute & FA_SYSTEM) ||
        IsDeletedLabel(entry))
    {
       return TRUE;
    }

    cluster = GetFirstCluster(&entry);
    if (cluster >= pipe->cluster)
    {
       if (!IsClusterMovable(handle, cluster, &isMovable))
          return FAIL;

       if (isMovable)
       {
          pipe->found = TRUE;
          pipe->clustertoplace = cluster;
          return FALSE;
       }
    }
    
    return TRUE;
}

static BOOL SelectFile(RDWRHandle handle, CLUSTER startfrom,
                       CLUSTER* clustertoplace)
{
    CLUSTER i;
    unsigned long LabelsInFat;
    char* FastSelectMap;
    
    FastSelectMap = GetFastSelectMap();
    if (!FastSelectMap)
       return SlowSelectFile(handle, startfrom, clustertoplace);
    
    LabelsInFat = GetLabelsInFat(handle);
    if (!LabelsInFat) return FAIL;
    
    for (i = startfrom; i < LabelsInFat; i++)
    {
        if (GetBitfieldBit(FastSelectMap, i))
        {
           *clustertoplace = i;
           return TRUE;
        }
    }
    
    return FALSE;
}

BOOL FullDefragPlace(RDWRHandle handle, CLUSTER clustertoplace,
                     CLUSTER clustertobereplaced, CLUSTER* stop)
{
    unsigned long filelen;
    BOOL pastTheEnd = FALSE, isMovable;
    CLUSTER label;

    /*
        Now try moving all the clusters to the front of the disk:
        - If the target cluster is free, just move it to the given location.
        - If the target cluster is not movable go on to the next cluster.
        - Otherwise swap the target cluster with the cluster in the file
          to defragment.
        - Do not move bad clusters.
        - Do not swap a cluster with itself.
    */
    
//    filelen = CalculateFileSize(handle, clustertoplace);  
//    if (filelen == FAIL) return FAIL;
    
//    if (!PreReadClusterSequence(handle, clustertobereplaced, filelen))
//       return FAIL;
//    if (!PreReadClusterChain(handle, clustertoplace))
//       return FAIL;
    
    clustertobereplaced--;
    while (!pastTheEnd)
    {
        do {
	   clustertobereplaced++;

           if (!GetNthCluster(handle, clustertobereplaced, &label))
              return FAIL;     

	   /* See wether the cluster is movable */
	   if (!IsClusterMovable(handle, clustertobereplaced, &isMovable))
	      return FAIL;

	} while (FAT_BAD(label) || !isMovable);

        DrawOnDriveMap(clustertoplace, READSYMBOL);
        DrawOnDriveMap(clustertobereplaced, WRITESYMBOL);

        SwapClustersInFastSelectMap(clustertoplace, clustertobereplaced);
        
	if (FAT_FREE(label))
	{
           if (!RelocateCluster(handle, clustertoplace, clustertobereplaced))
	      return FAIL;

           DrawOnDriveMap(clustertoplace, UNUSEDSYMBOL);
           DrawOnDriveMap(clustertobereplaced, OPTIMIZEDSYMBOL);
	}
	else
	{
	   if (clustertoplace != clustertobereplaced)
	   {
              if (!SwapClusters(handle, clustertoplace, clustertobereplaced))
                 return FAIL;
           }
           
           DrawOnDriveMap(clustertoplace, USEDSYMBOL);
           DrawOnDriveMap(clustertobereplaced, OPTIMIZEDSYMBOL);
        }
	clustertoplace = GetNthFileCluster(handle, clustertobereplaced, 1,
					   &pastTheEnd);
	if (!clustertoplace) return FAIL;
    }

    *stop = clustertobereplaced+1;
    return TRUE;
}