/*    
   RelocSqc.c - Function to relocate a sequence of clusters in a volume.

   Copyright (C) 2004 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 <assert.h>

#include "fte.h"

#define COPY_BLOCK_SIZE 64 /* Maximum sectors in a block */

static BOOL RelocateConsecutiveClusterSeq(RDWRHandle handle, CLUSTER source, CLUSTER destination, unsigned long flength);
static BOOL GetNextConsecutiveChain(RDWRHandle handle, CLUSTER source, unsigned long maxlength, unsigned long* length);
static BOOL MarkClusterChain(RDWRHandle handle, CLUSTER source, CLUSTER dest, unsigned long filesize, BOOL atend);
static BOOL CopyClusters(RDWRHandle handle, CLUSTER source, CLUSTER dest, unsigned long length);
static BOOL CleanFatLabels(RDWRHandle handle, CLUSTER dest, unsigned long length);

BOOL RelocateClusterSequence(RDWRHandle handle, CLUSTER source, CLUSTER destination, unsigned long length)
{
    unsigned long flength, origlength = length;
      
    /* Small optimization for when length == 1*/
    if (length == 1)
    {
	return RelocateCluster(handle, source, destination);
    }
    
    /* Change the structures for the relocation */
    while (length > 0)
    {    
        if (!GetNextConsecutiveChain(handle, source, length, &flength))	    
	   RETURN_FTEERROR(FALSE);		

	if (!RelocateConsecutiveClusterSeq(handle, source, destination, flength))
	    RETURN_FTEERROR(FALSE);
	
	source += flength;
	destination += flength;
	length -= flength;
    }
      
    /* Copy all the data */
    if (!CopyClusters(handle, source, destination, origlength))
	return FALSE;    
    
    return TRUE;
}

static BOOL RelocateConsecutiveClusterSeq(RDWRHandle handle, CLUSTER source, CLUSTER destination, unsigned long flength)
{
    CLUSTER label;	 
    
    assert(flength>0);
    
    if (!RelocateCluster(handle, source, destination))
	RETURN_FTEERROR(FALSE);
    
    if (flength == 1) return TRUE;              /* If we have to relocate just one cluster, we're done */
    
    /* After that, just move all the data from the source to the destination,
       adding fat labels to the destination */
    if (!GetNthCluster(handle, source+flength-1, &label))
	RETURN_FTEERROR(FALSE);
    
    if (!CopyClusters(handle, source+1, destination+1, flength-1))
	RETURN_FTEERROR(FALSE);    
    
    if (!MarkClusterChain(handle, source, destination, flength-1, FAT_LAST(label)))
	RETURN_FTEERROR(FALSE);      
  
    /* In the end, remove all the fat labels from the source */
    if (!CleanFatLabels(handle, source, flength))
	RETURN_FTEERROR(FALSE);        
    
    return TRUE;
}

static BOOL GetNextConsecutiveChain(RDWRHandle handle, CLUSTER source, unsigned long maxlength, unsigned long* length)
{
    CLUSTER current = source, label;
    unsigned long retLength;
    
    if (!GetNthCluster(handle, source, &label))
	RETURN_FTEERROR(FALSE);
    
    for (retLength=1; 
	 FAT_NORMAL(label) && (label == ++current) && (retLength < maxlength);
         retLength++)    
    {
	if (!GetNthCluster(handle, current, &label))
	    RETURN_FTEERROR(FALSE);
    }
    
    *length = retLength;
    return TRUE;    
}

static BOOL MarkClusterChain(RDWRHandle handle, CLUSTER source, CLUSTER dest, unsigned long filesize, BOOL atend)
{
    CLUSTER label;
    unsigned long i;    
    
    for (i=0; i<filesize; i++)
    {
	if (!WriteFatLabel(handle, dest+i, dest+i+1))
	    RETURN_FTEERROR(FALSE);
    }

    if (atend)
    {    		 
        if (!WriteFatLabel(handle, dest+filesize, FAT_LAST_LABEL))
	   RETURN_FTEERROR(FALSE);
    }    
    else
    {
	if (!GetNthCluster(handle, source+filesize, &label))
	    RETURN_FTEERROR(FALSE);

	if (!WriteFatLabel(handle, dest+filesize, label))
	    RETURN_FTEERROR(FALSE);
    }
     
    return TRUE;    
}

static BOOL CopyClusters(RDWRHandle handle, CLUSTER source, CLUSTER dest, unsigned long length)
{
    SECTOR ssource, sdest;
    unsigned long sectorspercluster, i, blocks, totallength;
    unsigned rest;    
    
    ssource = ConvertToDataSector(handle, source);
    if (!ssource) RETURN_FTEERROR(FALSE);
	
    sdest = ConvertToDataSector(handle, dest);
    if (!sdest) RETURN_FTEERROR(FALSE);
	
    sectorspercluster = GetSectorsPerCluster(handle);
    if (!sectorspercluster) RETURN_FTEERROR(FALSE);
    
    totallength = length * sectorspercluster;
    
    blocks = totallength / COPY_BLOCK_SIZE;
    rest   = (unsigned)(totallength % COPY_BLOCK_SIZE);    
    
    for (i=0; i<blocks; i++)
    {    
	if (!CopySectors(handle, ssource, sdest, COPY_BLOCK_SIZE))
	    RETURN_FTEERROR(FALSE);
    }
    
    if (rest > 0)
    {
	if (!CopySectors(handle, ssource, sdest, rest))
	    RETURN_FTEERROR(FALSE);	
    }
    
    return TRUE;    
}

static BOOL CleanFatLabels(RDWRHandle handle, CLUSTER dest, unsigned long length)
{
    unsigned long i;
    
    for (i=0; i<length; i++)
	if (!WriteFatLabel(handle, dest + i, FAT_FREE_LABEL))
	    RETURN_FTEERROR(FALSE);
	
    return TRUE;       
}
