#include "Defragment.h"


// Ahh yes I ripped this from my old Findupes project :)
// Fits a path name, composed of a path (i.e. "c:\blah\blah\cha\cha") and a filename ("stuff.txt")
// and fits it to a given length. If it has to truncate it will first truncate from the path,
// substituting in periods. So you might end up with something like:
// C:\Program Files\Micro...\Register.exe
int FitName (wchar_t *destination, size_t destsize, const wchar_t *path, const wchar_t *filename, uint32 totalWidth)
{
	uint32 pathLen=0;
	uint32 fnLen=0;
	uint32 halfTotLen=0;
	uint32 len4fn=0;     /* number of chars remaining for filename after path is applied */
	uint32 len4path=0;   /* number of chars for path before filename is applied          */
	wchar_t fmtStrPath[20]=L"";
	wchar_t fmtStrFile[20]=L"";
	wchar_t fmtString[40]=L"";

    /*
	assert (destination != NULL);
	assert (path != NULL);
	assert (filename != NULL);
	assert (totalWidth != 0);
    */

	pathLen = wcslen(path);
	fnLen = wcslen(filename);
	if (!(totalWidth % 2))
		halfTotLen=totalWidth / 2;
	else
		halfTotLen=(totalWidth-1) / 2;  /* -1 because otherwise (halfTotLen*2) ==
(totalWidth+1) which wouldn't be good */

	/* determine how much width the path and filename each get */
	if ( (pathLen >= halfTotLen) && (fnLen < halfTotLen) )
	{
		len4fn = fnLen;
		len4path = (totalWidth - len4fn);
	}

	if ( (pathLen < halfTotLen) && (fnLen < halfTotLen) )
	{
		len4fn = fnLen;
		len4path = pathLen;
	}

	if ( (pathLen >= halfTotLen) && (fnLen >= halfTotLen) )
	{
		len4fn = halfTotLen;
		len4path = halfTotLen;
	}

	if ( (pathLen < halfTotLen) && (fnLen >= halfTotLen) )
	{
		len4path = pathLen;
		len4fn = (totalWidth - len4path);
	}
	/*
		if halfTotLen was adjusted above to avoid a rounding error, give the
		extra wchar_t to the filename
	*/
	if (halfTotLen < (totalWidth/2)) len4path++;

	if (pathLen > len4path)	swprintf (fmtStrPath, L"%%.%ds...\\", len4path-4);
	else
		swprintf (fmtStrPath, L"%%s");

	if (fnLen > len4fn)	swprintf (fmtStrFile, L"%%.%ds...", len4fn-3);
	else
		swprintf (fmtStrFile, L"%%s");

	wcscpy (fmtString, fmtStrPath);
	wcscat (fmtString, fmtStrFile);
	/*swprintf (fmtString, L"%s%s", fmtStrPath, fmtStrFile);*/
	swprintf(destination, destsize, fmtString, path,filename);

	return (1);
}

Defragment::Defragment (wstring Name, DefragType DefragMethod)
{
    Method = DefragMethod;
    DoLimitLength = true;
    Error = false;
    Done = false;
    PleaseStop = false;
    PleasePause = false;
    DriveName = Name;
    StatusPercent = 0.0f;
    LastBMPUpdate = GetTickCount ();

    SetStatusString (L"Opening volume " + Name);
    if (!Volume.Open (Name))
    {
        SetStatusString (L"Error opening volume " + Name);
        Error = true;
        Done = true;
        StatusPercent = 100.0f;
    }

    return;
}


Defragment::~Defragment ()
{
    if (!IsDoneYet ())
    {
        Stop ();
        while (!IsDoneYet()  &&  !HasError())
        {
            SetStatusString (L"Waiting for thread to stop ...");
            Sleep (150);
        }
    }

    Volume.Close ();
    return;
}


void Defragment::SetStatusString (wstring NewStatus)
{
    Lock ();
    StatusString = NewStatus;
    Unlock ();

    return;
}


wstring Defragment::GetStatusString (void)
{
    wstring ReturnVal;

    Lock ();
    ReturnVal = StatusString;
    Unlock ();

    return (ReturnVal);
}


double Defragment::GetStatusPercent (void)
{
    return (StatusPercent);
}


bool Defragment::IsDoneYet (void)
{
    return (Done);
}


void Defragment::Start (void)
{
    uint32 i;
    uint64 FirstFreeLCN;
    uint64 TotalClusters;
    uint64 ClustersProgress;
    wchar_t PrintName[80];
    int Width = 70;

    if (Error)
        goto DoneDefrag;

    // First thing: build a file list.
    SetStatusString (L"Getting volume bitmap");
    if (!Volume.GetBitmap())
    {
        SetStatusString (L"Could not get volume " + DriveName + L" bitmap");
        Error = true;
        goto DoneDefrag;
    }

    LastBMPUpdate = GetTickCount ();

    if (PleaseStop)
        goto DoneDefrag;

    SetStatusString (L"Obtaining volume geometry");
    if (!Volume.ObtainInfo ())
    {
        SetStatusString (L"Could not obtain volume " + DriveName + L" geometry");
        Error = true;
        goto DoneDefrag;
    }

    if (PleaseStop)
        goto DoneDefrag;

    SetStatusString (L"Building file database for volume " + DriveName);
    if (!Volume.BuildFileList (PleaseStop, StatusPercent))
    {
        SetStatusString (L"Could not build file database for volume " + DriveName);
        Error = true;
        goto DoneDefrag;
    }

    if (PleaseStop)
        goto DoneDefrag;

    SetStatusString (L"Analyzing database for " + DriveName);
    TotalClusters = 0;
    for (i = 0; i < Volume.GetDBFileCount(); i++)
    {
        TotalClusters += Volume.GetDBFile(i).Clusters;
    }

    // Defragment!
    ClustersProgress = 0;

    // Find first free LCN for speedier searches ...
    Volume.FindFreeRange (0, 1, FirstFreeLCN);

    if (PleaseStop)
        goto DoneDefrag;

    // Analyze?
    if (Method == DefragAnalyze)
    {
        uint32 j;

        Report.RootPath = Volume.GetRootPath ();

        Report.FraggedFiles.clear ();
        Report.UnfraggedFiles.clear ();
        Report.UnmovableFiles.clear ();

        Report.FilesCount = Volume.GetDBFileCount () - Volume.GetDBDirCount ();
        Report.DirsCount = Volume.GetDBDirCount ();
        Report.DiskSizeBytes = Volume.GetVolumeInfo().TotalBytes;

        Report.FilesSizeClusters = 0;
        Report.FilesSlackBytes = 0;
        Report.FilesSizeBytes = 0;
        Report.FilesFragments = 0;

        for (j = 0; j < Volume.GetDBFileCount(); j++)
        {
            FileInfo Info;

            Info = Volume.GetDBFile (j);

            Report.FilesFragments += max ((size_t)1, Info.Fragments.size()); // add 1 fragment even for 0 bytes/0 cluster files

            if (Info.Attributes.Process == 0)
                continue;

            SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);

            Report.FilesSizeClusters += Info.Clusters;
            Report.FilesSizeBytes += Info.Size;

            if (Info.Attributes.Unmovable == 1)
                Report.UnmovableFiles.push_back (j);

            if (Info.Fragments.size() > 1)
                Report.FraggedFiles.push_back (j);
            else
                Report.UnfraggedFiles.push_back (j);

            StatusPercent = ((double)j / (double)Report.FilesCount) * 100.0f;
        }

        Report.FilesSizeOnDisk = Report.FilesSizeClusters * (uint64)Volume.GetVolumeInfo().ClusterSize;
        Report.FilesSlackBytes = Report.FilesSizeOnDisk - Report.FilesSizeBytes;
        Report.AverageFragments = (double)Report.FilesFragments / (double)Report.FilesCount;
        Report.PercentFragged = 100.0f * ((double)(signed)Report.FraggedFiles.size() / (double)(signed)Report.FilesCount);

        uint64 Percent;
        Percent = (10000 * Report.FilesSlackBytes) / Report.FilesSizeOnDisk;
        Report.PercentSlack = (double)(signed)Percent / 100.0f;
    }
    else
    // Go through all the files and ... defragment them!
    for (i = 0; i < Volume.GetDBFileCount(); i++)
    {
        FileInfo Info;
        bool Result;
        uint64 TargetLCN;
        uint64 PreviousClusters;

        // What? They want us to pause? Oh ok.
        if (PleasePause)
        {
            SetStatusString (L"Paused");
            PleasePause = false;

            while (PleasePause == false)
            {
                Sleep (50);
            }

            PleasePause = false;
        }

        if (PleaseStop)
        {
            SetStatusString (L"Stopping");
            break;
        }

        //
        Info = Volume.GetDBFile (i);

        PreviousClusters = ClustersProgress;
        ClustersProgress += Info.Clusters;

        if (Info.Attributes.Process == 0)
            continue;

        if (!DoLimitLength)
            SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);
        else
        {
            FitName (PrintName, _countof(PrintName), Volume.GetDBDir (Info.DirIndice).c_str(), Info.Name.c_str(), Width);
            SetStatusString (PrintName);
        }

        // Calculate percentage complete
        StatusPercent = 100.0f * double((double)PreviousClusters / (double)TotalClusters);

        // Can't defrag directories yet
        if (Info.Attributes.Directory == 1)
            continue;

        // Can't defrag 0 byte files :)
        if (Info.Fragments.empty())
            continue;

        // If doing fast defrag, skip non-fragmented files
        // Note: This assumes that the extents stored in Info.Fragments
        //       are consolidated. I.e. we assume it is NOT the case that
        //       two extents account for a sequential range of (non-
        //       fragmented) clusters.
        if (Info.Fragments.size() == 1  &&  Method == DefragFast)
            continue;

        // Otherwise, defrag0rize it!
        int Retry = 3;  // retry a few times
        while (Retry > 0)
        {
            // Find a place that can fit the file
            Result = Volume.FindFreeRange (FirstFreeLCN, Info.Clusters, TargetLCN);

            // If yes, try moving it
            if (Result)
            {
                // If we're doing an extensive defrag and the file is already defragmented
                // and if its new location would be after its current location, don't
                // move it.
                if (Method == DefragExtensive  &&  Info.Fragments.size() == 1  &&
                    TargetLCN > Info.Fragments[0].StartLCN)
                {
                    Retry = 1;
                }
                else
                {
                    if (Volume.MoveFileDumb (i, TargetLCN))
                    {
                        Retry = 1; // yay, all done with this file.
                        Volume.FindFreeRange (0, 1, FirstFreeLCN);
                    }
                }
            }

            // New: Only update bitmap if it's older than 15 seconds
            if ((GetTickCount() - LastBMPUpdate) < 15000)
                Retry = 1;
            else
            if (!Result  ||  Retry != 1)
            {   // hmm. Wait for a moment, then update the drive bitmap
                //SetStatusString (L"(Reobtaining volume " + DriveName + L" bitmap)");

                if (!DoLimitLength)
                {
                    SetStatusString (GetStatusString() + wstring (L" ."));
                }

                if (Volume.GetBitmap ())
                {
                    LastBMPUpdate = GetTickCount ();

                    if (!DoLimitLength)
                        SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);
                    else
                        SetStatusString (PrintName);

                    Volume.FindFreeRange (0, 1, FirstFreeLCN);
                }
                else
                {
                    SetStatusString (L"Could not re-obtain volume " + DriveName + L" bitmap");
                    Error = true;
                }
            }

            Retry--;
        }

        if (Error == true)
            break;
    }

DoneDefrag:
    wstring OldStatus;

    OldStatus = GetStatusString ();
    StatusPercent = 99.999999f;
    SetStatusString (L"Closing volume " + DriveName);
    Volume.Close ();
    StatusPercent = 100.0f;

    // If there was an error then the wstring has already been set
    if (Error)
        SetStatusString (OldStatus);
    else
    if (PleaseStop)
        SetStatusString (L"Volume " + DriveName + L" defragmentation was stopped");
    else
        SetStatusString (L"Finished defragmenting " + DriveName);

    Done = true;

    return;
}


void Defragment::TogglePause (void)
{
    Lock ();
    SetStatusString (L"Pausing ...");
    PleasePause = true;
    Unlock ();

    return;
}


void Defragment::Stop (void)
{
    Lock ();
    SetStatusString (L"Stopping ...");
    PleaseStop = true;
    Unlock ();

    return;
}


bool Defragment::HasError (void)
{
    return (Error);
}

