/*!
    \file  Fat12FileSystem.cpp
    \brief Fat12FileSystem

    Copyright (c) 2005 HigePon
    WITHOUT ANY WARRANTY

    \author  HigePon
    \version $Revision$
    \date   create:2005/03/26 update:$Date$
*/

#include <string.h>
#include <stdio.h>
#include "Fat12FileSystem.h"
#include "Fat12.h"
#include "Assert.h"

using namespace MonAPI;

//#define DEBUG_READ_TRACE

namespace FatFS
{

/*----------------------------------------------------------------------
    Fat12FileSystem
----------------------------------------------------------------------*/
Fat12FileSystem::Fat12FileSystem(IStorageDevice* fd)
{
    this->fd        = fd;
    this->lastError = NO_ERROR;
}

Fat12FileSystem::~Fat12FileSystem()
{
    delete[] this->sectorBuffer;
    delete this->fat;
    if (this->rootEntryBuffer) delete[] this->rootEntryBuffer;
    DeleteEntry(this->rootDirectory);
}

bool Fat12FileSystem::Initialize()
{
    /* allocate sector buffer */
    this->sectorBuffer = new byte[SECTOR_SIZE];

    if (this->sectorBuffer == NULL)
    {
        this->lastError = FileSystemError::MEMORY_ALLOCATE_ERROR;
        return false;
    }

    /* fat */
    this->fat = new Fat12(this->fd);

    if (this->fat == NULL)
    {
        this->lastError = FileSystemError::MEMORY_ALLOCATE_ERROR;
        return false;
    }

    if (!this->fat->Initialize())
    {
        this->lastError = this->fat->GetLastError();
        return false;
    }

    /* read root directory */
    if (!ReadRootDirectory()) return false;

    /* create directory cache */
    if (!CreateDirectoryCache()) return false;

    return true;
}

bool Fat12FileSystem::Read(dword lba, byte* buffer, dword size)
{
    return this->fd->read(lba, buffer, size) == 0;
}

File* Fat12FileSystem::Open(const CString& path, int mode)
{
    Fat12Directory* directoryEntry;
    CString fileName;

    int lastIndexOfSlash = path.lastIndexOf('/');

    if (lastIndexOfSlash == -1)
    {
        directoryEntry = this->rootDirectory;
        fileName = path;
    }
    else
    {
        CString directoryPath = path.substring(0, lastIndexOfSlash);
        fileName  = path.substring(lastIndexOfSlash + 1, path.getLength() - lastIndexOfSlash);
        directoryEntry = FindDirectoryEntry(this->rootDirectory, directoryPath);

        if (directoryEntry == NULL) return NULL;
    }

    Fat12File* file = FindFileEntry(directoryEntry, fileName);
    if (file == NULL) return NULL;

    return file;
}

bool Fat12FileSystem::Close(File* file)
{
    delete file;
    return false;
}

bool Fat12FileSystem::CreateFile(const CString& path)
{
    return false;
}

bool Fat12FileSystem::RemoveFile(const CString& path)
{
    int lastIndexOfSlash = path.lastIndexOf('/');

    if (lastIndexOfSlash == -1)
    {
	Fat12File* file = FindFileEntry(this->rootDirectory, path);
	if (file == NULL)
	{
	    this->lastError = FileSystemError::FILE_NOT_FOUND;
	    return false;
	}
	return RemoveFileFromRootDirectory(file);
    }
    else
    {
        CString directoryPath = path.substring(0, lastIndexOfSlash);
        CString fileName      = path.substring(lastIndexOfSlash + 1, path.getLength() - lastIndexOfSlash);
        Fat12Directory* directoryEntry = FindDirectoryEntry(this->rootDirectory, directoryPath);

        if (directoryEntry == NULL)
	{
	    this->lastError = FileSystemError::DIRECTORY_NOT_FOUND;
	    return false;
	}

	Fat12File* file = FindFileEntry(directoryEntry, fileName);
	if (file == NULL)
	{
	    this->lastError = FileSystemError::FILE_NOT_FOUND;
	    return false;
	}
	return directoryEntry == this->rootDirectory ? RemoveFileFromRootDirectory(file) : RemoveFileFromDirectory(directoryEntry, file);
    }
}


bool Fat12FileSystem::CreateDirectory(const CString& path)
{
    return false;
}

bool Fat12FileSystem::RemoveDirectory(const CString& path)
{
    return false;
}

_A<FileSystemEntry*> Fat12FileSystem::GetFileSystemEntries(const CString& path)
{
    _A<FileSystemEntry*> noResult(0);

    Fat12Directory* entry = FindDirectoryEntry(this->rootDirectory, path);
    if (entry == NULL)
    {
        return noResult;
    }

    _A<FileSystemEntry*> ret(entry->children.size());

    for (int i = 0; i < ret.get_Length(); i++)
    {
        ret[i] = entry->children[i];
    }

    return ret;
}

/*----------------------------------------------------------------------
    Fat12FileSystem private functions
----------------------------------------------------------------------*/
Fat12Directory* Fat12FileSystem::FindDirectoryEntry(Fat12Directory* root, const CString& path)
{
    bool found;
    _A<CString> elements = path.split('/');

    FOREACH (CString, element, elements)
    {
        if (element == ".")
        {
            continue;
        }
        else if (element == "..")
        {
            root = root->parent;
            continue;
        }
        else if (element == "" || element == NULL)
        {
            continue;
        }

        found = false;
        for (int i = 0; i < root->children.size(); i++)
        {
            if (root->children[i]->GetName() == element)
            {
                root = (Fat12Directory*)root->children[i];
                found = true;
                break;
            }
        }
        if (!found) return NULL;
    }
    END_FOREACH

    return root;
}

// return value should be delete, when close
Fat12File* Fat12FileSystem::FindFileEntry(Fat12Directory* directory, const CString& fileName)
{
    for (int i = 0; i < directory->children.size(); i++)
    {
        FileSystemEntry* entry = directory->children[i];

        if (entry->GetName() == fileName && !entry->IsDirectory())
        {
            Fat12File* file = (Fat12File*)entry;

            /* use default copy constructor */
            return new Fat12File(*file);
        }
    }
    return NULL;
}

// should be size % sector size = 0
int Fat12FileSystem::ReadAtOnce(dword lba, void* buffer, dword size)
{
    dword sectors = size / SECTOR_SIZE;
    for (dword i = 0; i < sectors; i++)
    {
        int result = this->fd->read(i + lba, (void*)((byte*)buffer + SECTOR_SIZE * i), SECTOR_SIZE);
        if (result)
        {
            return result;
        }
    }
    return 0;
}

bool Fat12FileSystem::ReadRootDirectory()
{
    this->rootEntryBuffer = ReadRootDirectoryIntoBuffer();

    if (this->rootEntryBuffer == NULL) return false;

    /* read root directory */
    this->rootDirectory = new Fat12Directory();

    DirectoryEntry* entry = (DirectoryEntry*)this->rootEntryBuffer;
    for (dword j = 0; j <  this->fat->GetMaxRootEntries(); j++, entry++)
    {
        if (entry->filename[0] == 0x00 || entry->filename[0] == 0xe5)
        {
            continue;
        }

        /* add to list of children */
        FileSystemEntry* fileSystemEntry = SetupFileSystemEntryInformation(this->rootDirectory, entry, 2);
        this->rootDirectory->children.add(fileSystemEntry);
    }
    return true;
}

FileSystemEntry* Fat12FileSystem::SetupFileSystemEntryInformation(Fat12Directory* parent, DirectoryEntry* entry, dword cluster)
{
    if (entry->attributes & ATTR_DIRECTORY)
    {
        Fat12Directory* directory = new Fat12Directory();
        FileDate* date= directory->GetCreateDate();

        SetupDate(date, entry);
        directory->SetName(GetProperName((const char*)entry->filename, (const char*)entry->extension));
        directory->SetCluster(entry->startCluster);
        directory->SetParentEntryCluster(cluster);
        directory->parent = parent;

        return directory;
    }
    else
    {
        Fat12File* file = new Fat12File(this);
        FileDate* date = file->GetCreateDate();

        SetupDate(date, entry);
        file->SetName(GetProperName((const char*)entry->filename, (const char*)entry->extension));
        file->SetSize(entry->fileSize);
        file->SetCluster(entry->startCluster);
        file->SetParentEntryCluster(cluster);
        file->parent = parent;
        return file;
    }
}

void Fat12FileSystem::SetupDate(FileDate* date, DirectoryEntry* entry)
{
    date->SetHour((entry->time >> 11) & 0x1F);
    date->SetMinute((entry->time >> 5) & 0x3F);

    date->SetSecond((entry->time & 0x1F) * 2);

    date->SetYear((entry->date >> 9) + 1980);
    date->SetMonth((entry->date >> 5) & 0x0f);
    date->SetDay(entry->date & 0x1F);
}

CString Fat12FileSystem::Trim(const char* str, int maxLength)
{
    int length = maxLength;

    for (int i = 0; i < maxLength; i++)
    {
        if (str[i] == ' ')
        {
            length = i;
            break;
        }
    }

    return CString(str, length);
}

CString Fat12FileSystem::GetProperName(const char* filename, const char* extension)
{
    CString name = Trim(filename, 8);
    CString ext  = Trim(extension, 3);

    if (ext.getLength() == 0)
    {
        return name;
    }
    else
    {
        return name + "." + ext;
    }
}

bool Fat12FileSystem::CreateDirectoryCache()
{
    for (int i = 0; i < this->rootDirectory->children.size(); i++)
    {
        FileSystemEntry* entry = this->rootDirectory->children[i];

        if (!entry->IsDirectory()) continue;

        Fat12Directory* directory = (Fat12Directory*)entry;

        bool result = CreateDirectoryCache(directory, directory->GetCluster());

        if (!result) return false;
    }
    return true;
}

bool Fat12FileSystem::CreateDirectoryCache(Fat12Directory* startDirectory, dword cluster)
{
    if (cluster >= 0xff8 && cluster <= 0xfff) return true;

    if (startDirectory->GetName() == "." || startDirectory->GetName() == "..") return true;

    byte* buffer = new byte[SECTOR_SIZE];

    if (ReadAtOnce(this->fat->GetLbaFromCluster(cluster), buffer, SECTOR_SIZE))
    {
        this->lastError = FileSystemError::DEVICE_READ_ERROR;
        delete[] buffer;
        return false;
    }

    DirectoryEntry* entry = (DirectoryEntry*)buffer;
    for (dword j = 0; j <  SECTOR_SIZE / sizeof(DirectoryEntry); j++, entry++)
    {
        if (entry->filename[0] == 0x00 || entry->filename[0] == 0xe5)
        {
            continue;
        }

        /* add to list of children */
        FileSystemEntry* fileSystemEntry = SetupFileSystemEntryInformation(startDirectory, entry, cluster);
        startDirectory->children.add(fileSystemEntry);

        /* directory? */
        if (!fileSystemEntry->IsDirectory()) continue;

        Fat12Directory* directory = (Fat12Directory*)fileSystemEntry;

        if (!CreateDirectoryCache(directory, directory->GetCluster()))
        {
            delete[] buffer;
            return false;
        }
    }

    /* has next cluster? */
    dword next = this->fat->GetNextCluster(cluster);
    if (next != cluster)
    {
        if (!CreateDirectoryCache(startDirectory, next))
        {
            delete[] buffer;
            return false;
        }
    }

    delete[] buffer;
    return true;
}

void Fat12FileSystem::DeleteEntry(FileSystemEntry* entry)
{
    if (!entry->IsDirectory())
    {
        delete entry;
        return;
    }

    Fat12Directory* directory = (Fat12Directory*)entry;

    for (int i = 0; i < directory->children.size(); i++)
    {
        DeleteEntry(directory->children[i]);
    }

    delete entry;
}

bool Fat12FileSystem::ReadClusterChain(dword startCluster, dword postion, void* buffer, dword size)
{
    dword clusterCountToDataStart = postion / SECTOR_SIZE;
    dword clusterCountToDataEnd   = (postion + size) / SECTOR_SIZE;
    dword restSize = size;

#ifdef DEBUG_READ_TRACE
    printf("startCluster=%d\n", startCluster);
    printf("postion=%d\n", postion);
    printf("size=%d\n", size);
    printf("clusterCountToDataStart=%d\n", clusterCountToDataStart);
    printf("clusterCountToDataEnd=%d\n", clusterCountToDataEnd);
    fflush(stdout);
#endif

    dword readTargetCluster;

    byte* buf = (byte*)buffer;

    if (!this->fat->GetClusterFromChain(startCluster, clusterCountToDataStart, &readTargetCluster))
    {
        return false;
    }

    int result = this->fd->read(this->fat->GetLbaFromCluster(readTargetCluster), this->sectorBuffer, SECTOR_SIZE);
    if (result)
    {
        return false;
    }

    dword firstReadSize;
    dword firstSectorStart = postion % SECTOR_SIZE;
    dword firstReadStart;

    if (firstSectorStart == 0)
    {
        firstReadSize = size < SECTOR_SIZE ? size : SECTOR_SIZE;
        firstReadStart = 0;
    }
    else
    {
        if (firstSectorStart + size < SECTOR_SIZE)
        {
            firstReadStart = firstSectorStart;
            firstReadSize = SECTOR_SIZE - firstSectorStart;
        }
        else
        {
            firstReadStart = firstSectorStart;
            firstReadSize = size;
        }
    }

    memcpy(buf, this->sectorBuffer + firstSectorStart, firstReadSize);

    restSize -= firstReadSize;
    buf += firstReadSize;

    for (dword i = clusterCountToDataStart + 1; i <= clusterCountToDataEnd; i++)
    {
        if (!this->fat->GetClusterFromChain(startCluster, i, &readTargetCluster))
        {
            return false;
        }

        int result = this->fd->read(this->fat->GetLbaFromCluster(readTargetCluster), this->sectorBuffer, SECTOR_SIZE);
        if (result)
        {
            return false;
        }

        if (restSize < SECTOR_SIZE)
        {
            memcpy(buf, this->sectorBuffer, restSize);
            return true;
        }
        else
        {
            memcpy(buf, this->sectorBuffer, SECTOR_SIZE);
            buf += SECTOR_SIZE;
            restSize -= SECTOR_SIZE;
        }
    }

    return true;
}

dword Fat12FileSystem::GetDirectoryBufferSize(Fat12Directory* directory)
{
    dword startCluster = directory->GetCluster();

    for (int i = 0; i < CHAIN_MAX; i++)
    {
        dword nextCluster = this->fat->GetNextCluster(startCluster);

        if (nextCluster == startCluster) return (i * SECTOR_SIZE);

        startCluster = nextCluster;
    }

    return 0;
}

bool Fat12FileSystem::ReadDirectoryAtOnce(Fat12Directory* directory, void* buffer)
{
    dword startCluster = directory->GetCluster();

    byte* buf = (byte*)buffer;

    for (int i = 0; i < CHAIN_MAX; i++)
    {
        int result = this->fd->read(this->fat->GetLbaFromCluster(startCluster), this->sectorBuffer, SECTOR_SIZE);
        if (result)
        {
            this->lastError = FileSystemError::DEVICE_READ_ERROR;
            return false;
        }

        memcpy(buf + i * SECTOR_SIZE, this->sectorBuffer, SECTOR_SIZE);

        dword nextCluster = this->fat->GetNextCluster(startCluster);

        if (nextCluster == startCluster) return true;

        startCluster = nextCluster;
    }

    printf("%s:%d\n", __FILE__, __LINE__);
    return false;
}

byte* Fat12FileSystem::ReadRootDirectoryIntoBuffer()
{
    dword rootEntrySectors  = this->fat->GetMaxRootEntries() * sizeof(DirectoryEntry) / SECTOR_SIZE;
    this->rootEntryStart    = this->fat->GetSectorsPerFat() * this->fat->GetFatCount() + 1;
    dword rootEntryBytes    = rootEntrySectors * SECTOR_SIZE;

    byte* rootEntryBuffer = new byte[rootEntryBytes];
    if (rootEntryBuffer == NULL)
    {
        this->lastError = MEMORY_ALLOCATE_ERROR;
        return NULL;
    }

    /* read */
    if (ReadAtOnce(rootEntryStart, rootEntryBuffer, rootEntryBytes))
    {
        delete[] this->rootEntryBuffer;
        this->lastError = FileSystemError::DEVICE_READ_ERROR;
        return NULL;
    }
    return rootEntryBuffer;
}

bool Fat12FileSystem::RemoveFileFromRootDirectory(Fat12File* file)
{
    DirectoryEntry* entry = (DirectoryEntry*)this->rootEntryBuffer;
    bool entryFound = false;
    for (dword j = 0; j <  this->fat->GetMaxRootEntries(); j++, entry++)
    {
        if (entry->filename[0] == 0x00 || entry->filename[0] == 0xe5)
        {
            continue;
        }

        CString name = GetProperName((const char*)entry->filename, (const char*)entry->extension);
        if (name != file->GetName()) continue;

        /* save */
        entry->filename[0] = 0xe5; /* mark as not used */
	entryFound = true;
        int result = this->fd->write(this->rootEntryStart + (j * sizeof(DirectoryEntry) / SECTOR_SIZE)
				     , &this->rootEntryBuffer[(j * sizeof(DirectoryEntry) /SECTOR_SIZE) / SECTOR_SIZE]
				     , SECTOR_SIZE);
        if (result)
        {
            this->lastError = FileSystemError::DEVICE_WRITE_ERROR;
            delete file;
            return false;
        }
    }

    delete file;
    return entryFound;
}

bool Fat12FileSystem::RemoveFileFromDirectory(Fat12Directory* directory, Fat12File* file)
{
    if (ReadAtOnce(this->fat->GetLbaFromCluster(file->GetParentEntryCluster()), this->sectorBuffer, SECTOR_SIZE))
    {
        this->lastError = FileSystemError::DEVICE_READ_ERROR;
        return false;
    }

    DirectoryEntry* entry = (DirectoryEntry*)this->sectorBuffer;

    byte entryCount = 0;
    bool entryFound = false;
    for (dword j = 0; j <  SECTOR_SIZE / sizeof(DirectoryEntry); j++, entry++)
    {
        entryCount++;

        if (entry->filename[0] == 0x00 || entry->filename[0] == 0xe5)
        {
            continue;
        }

        CString name = GetProperName((const char*)entry->filename, (const char*)entry->extension);

        if (name != file->GetName()) continue;

        /* save */
        entry->filename[0] = 0xe5; /* mark as not used */
	entryFound = true;
        int result = this->fd->write(this->fat->GetLbaFromCluster(file->GetParentEntryCluster()), this->sectorBuffer, SECTOR_SIZE);
        if (result)
        {
            this->lastError = FileSystemError::DEVICE_WRITE_ERROR;
            delete file;
            return false;
        }

        entryCount--;
    }

    if (!entryFound)
    {
	delete file;
	return false;
    }

    if (entryCount == 0)
    {
        dword previousCluster;

        if (!fat->GetPreviousCluster(directory->GetCluster(), file->GetParentEntryCluster(), &previousCluster))
        {
            delete file;

            /* fat broken ? */
            printf("%s:%d\n", __FILE__, __LINE__);
            return false;
        }

        dword nextCluster = fat->GetNextCluster(file->GetParentEntryCluster());

        fat->SetNextCluster(previousCluster, nextCluster);

#define UNUSED_CLUSTER 0
        fat->SetNextCluster(file->GetParentEntryCluster(), UNUSED_CLUSTER);

        this->fat->Flush();

    }
    delete file;
    return true;
}



}; /* namespace FatFS */
