#include "File.h"

#include "Key.h"
#include "Text.h"
#include "Timer.h"
#ifdef WIN32
#    include <direct.h>
#    include <Shlwapi.h>
#    pragma comment(lib, "Shlwapi.lib")
#else
#    include <dirent.h>
#    include <stdio.h>
#    include <sys/stat.h>
#endif

using namespace Framework;
using namespace Encryption;

// Content of the File class from File.h
// Constructor
File::File()
    : ReferenceCounter(),
      stream(0),
      pfad(0),
      gr(0),
      tmpReadByte(0),
      tmpReadBPos(7),
      tmpWriteByte(0),
      tmpWriteBPos(-1),
      key(0)
{}

//! Constructor
File::File(const char* pfad)
    : File()
{
    setFile(pfad);
}

//! Constructor
File::File(Text* pfad)
    : File()
{
    setFile(pfad);
}

// Destructor
File::~File()
{
    if (key) key->release();
    if (stream) delete stream;
    if (pfad) pfad->release();
}

// non-constant
void File::setFile(const char* pfad) // sets the file
{
    if (isOpen()) close();
    if (!this->pfad) this->pfad = new Text();
    this->pfad->setText(pfad);
    gr = 0;
}

void File::setFile(Text* pfad)
{
    if (isOpen()) close();
    if (!this->pfad) this->pfad = new Text();
    this->pfad->setText(*pfad);
    pfad->release();
    gr = 0;
}

bool File::rename(const char* pfad) // renames and possibly moves the file
{
    if (!pfad) return 0;
    if (FileRename(this->pfad->getText(), pfad))
    {
        this->pfad->setText(pfad);
        return 1;
    }
    return 0;
}

bool File::rename(Text* pfad)
{
    if (!this->pfad)
    {
        pfad->release();
        return 0;
    }
    if (FileRename(this->pfad->getText(), pfad->getText()))
    {
        this->pfad->setText(*pfad);
        pfad->release();
        return 1;
    }
    pfad->release();
    return 0;
}

bool File::remove() // deletes the file
{
    if (!pfad) return 0;
    return FileRemove(dynamic_cast<Text*>(pfad->getThis()));
}

bool File::create() // creates the file
{
    if (!pfad) return 0;
    return FilePathCreate(dynamic_cast<Text*>(pfad->getThis()));
}

bool File::open(int style) // opens the file
{
    if (!pfad) return 0;
    if (stream) delete stream;
    stream = new std::fstream();
    std::ios_base::openmode om = std::ios::binary;
    if ((style | Style::read) == style) om |= std::ios::in;
    if ((style | Style::write) == style) om |= std::ios::out;
    stream->open(pfad->getText(), om);
    if ((style | Style::end) == style)
    {
        if ((style | Style::read) == style) stream->seekg(0, std::ios::end);
        if ((style | Style::write) == style) stream->seekp(0, std::ios::end);
    }
    if (!stream->is_open() || !stream->good())
    {
        delete stream;
        stream = 0;
        return 0;
    }
    tmpReadBPos = 7;
    tmpWriteBPos = -1;
    return 1;
}

void File::setReadPosition(__int64 pos, bool ende) // sets the read position
{
    if (!pfad) return;
    if (stream)
    {
        if (ende)
            stream->seekg(pos, std::ios::end);
        else
            stream->seekg(pos, std::ios::beg);
    }
    tmpReadBPos = 7;
}

void File::setWritePosition(__int64 pos, bool ende) // sets the write position
{
    if (!pfad) return;
    if (stream)
    {
        if (ende)
            stream->seekp(pos, std::ios::end);
        else
            stream->seekp(pos, std::ios::beg);
    }
    tmpWriteBPos = -1;
}

void File::write(const char* bytes, int len) // writes bytes to file
{
    if (!pfad || !stream) return;
    if (tmpWriteBPos >= 0)
    {
        tmpWriteBPos = -1;
        stream->write(&tmpWriteByte, 1);
        tmpWriteByte = 0;
    }
    if (key)
    {
        key->setPos(getWritePosition());
        Bytes* n = new Bytes(bytes, len);
        key->encode(dynamic_cast<Bytes*>(n->getThis()));
        stream->write(n->getBytes(), len);
        n->release();
    }
    else
        stream->write(bytes, len);
}

void Framework::File::flush()
{
    if (!pfad || !stream) return;
    stream->flush();
}

void File::read(char* bytes, int len) // reads bytes from file
{
    if (!pfad) return;
    if (stream)
    {
        __int64 tmp = getReadPosition();
        stream->read(bytes, len);
        if (key)
        {
            key->setPos(tmp);
            Bytes* n = new Bytes();
            n->setBytesZ(bytes, len);
            key->decode(n);
        }
    }
    tmpReadBPos = 7;
    tmpWriteBPos = -1;
}

Text* File::readLine() // reads a line
{
    if (!pfad || !stream) return 0;
    if (isEnd()) return 0;
    Text* ret = new Text("");
    __int64 len = getSize();
    for (char c = 0; c != '\n' && stream->tellg() < len;)
    {
        __int64 tmp = getReadPosition();
        stream->read(&c, 1);
        if (key)
        {
            key->setPos(tmp);
            Bytes* n = new Bytes();
            n->setBytesZ(&c, 1);
            key->decode(n);
        }
        if (c) ret->append(&c, 1);
    }
    tmpWriteBPos = 7;
    tmpWriteBPos = -1;
    return ret;
}

void File::close() // closes the file
{
    if (!pfad || !stream) return;
    if (tmpWriteBPos >= 0)
    {
        if (key)
        {
            key->setPos(getWritePosition());
            Bytes* n = new Bytes(&tmpWriteByte, 1);
            key->encode(dynamic_cast<Bytes*>(n->getThis()));
            stream->write(n->getBytes(), 1);
            n->release();
        }
        else
            stream->write(&tmpWriteByte, 1);
    }
    stream->close();
    delete stream;
    stream = 0;
}

#ifdef WIN32
bool File::setLetzteAEnderung(
    Time* zeit) // sets the modification date of the file
{
    if (!pfad)
    {
        zeit->release();
        return 0;
    }
    HANDLE hFile = CreateFile(pfad->getText(),
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        zeit->release();
        return 0;
    }
    FILETIME ftCreate, ftAccess, ftWrite;
    if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
    {
        CloseHandle(hFile);
        zeit->release();
        return 0;
    }
    SYSTEMTIME stUTC, stLocal;
    stLocal.wMilliseconds = 0;
    stLocal.wSecond = zeit->zClock()->getSekunde();
    stLocal.wMinute = zeit->zClock()->getMinute();
    stLocal.wHour = zeit->zClock()->getStunde();
    stLocal.wDay = zeit->zDate()->getTag();
    stLocal.wMonth = zeit->zDate()->getMonat();
    stLocal.wYear = zeit->zDate()->getJahr();
    zeit->release();
    if (!TzSpecificLocalTimeToSystemTime(NULL, &stLocal, &stUTC))
    {
        CloseHandle(hFile);
        return 0;
    }
    if (!SystemTimeToFileTime(&stUTC, &ftWrite))
    {
        CloseHandle(hFile);
        return 0;
    }
    if (!SetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
    {
        CloseHandle(hFile);
        return 0;
    }
    CloseHandle(hFile);
    return 1;
}
#endif

bool File::getNextBit(bool& bit) // read file bit by bit
{
    if (!pfad || !stream) return 0;
    if (tmpReadBPos == 7)
    {
        tmpReadBPos = -1;
        __int64 tmp = getReadPosition();
        stream->read(&tmpReadByte, 1);
        if (key)
        {
            key->setPos(tmp);
            Bytes* n = new Bytes();
            n->setBytesZ(&tmpReadByte, 1);
            key->decode(n);
        }
    }
    tmpReadBPos++;
    bit = (tmpReadByte >> (7 - tmpReadBPos)) & 1;
    return 1;
}

bool File::setNextBit(bool bit) // write file bit by bit
{
    if (!pfad || !stream) return 0;
    tmpWriteBPos++;
    tmpWriteByte |= (char)(((char)bit << (7 - tmpWriteBPos))
                           & (1 << (7 - tmpWriteBPos)));
    if (tmpWriteBPos == 7)
    {
        tmpWriteBPos = -1;
        if (key)
        {
            key->setPos(getWritePosition());
            Bytes* n = new Bytes(&tmpWriteByte, 1);
            key->encode(dynamic_cast<Bytes*>(n->getThis()));
            stream->write(n->getBytes(), 1);
            n->release();
        }
        else
            stream->write(&tmpWriteByte, 1);
        tmpWriteByte = 0;
    }
    return 1;
}

// Sets the encryption key for the file
void File::setKey(char* s, int l)
{
    if (l == 0)
    {
        key = (Key*)key->release();
        return;
    }
    if (key)
        key->setKey(s, l);
    else
        key = new Key(s, l);
}

// constant
bool File::isDirectory() const // checks if the file is a directory
{
    if (!pfad) return 0;
    return FileIsDirectory(dynamic_cast<Text*>(pfad->getThis()));
}

bool File::isOpen() const // checks if the file is open
{
    if (!pfad) return 0;
    if (stream) return stream->is_open() && stream->good();
    return 0;
}

int File::getSubFileCount() const // returns the number of sub-files
{
#ifdef WIN32
    if (!pfad) return 0;
    if (!FileIsDirectory(dynamic_cast<Text*>(pfad->getThis()))) return 0;
    int ret = 0;
    HANDLE fHandle;
    WIN32_FIND_DATA wfd;
    Text stxt = pfad->getText();
    stxt.replace('/', '\\');
    if (stxt.positionOf('\\') == stxt.getLength() - 1)
        stxt.append("*");
    else
        stxt.append("\\*");
    fHandle = FindFirstFile(stxt.getText(), &wfd);
    FindNextFile(fHandle, &wfd);
    while (FindNextFile(fHandle, &wfd))
        ++ret;
    FindClose(fHandle);
    return ret;
#else
    if (!pfad) return 0;
    if (!FileIsDirectory(dynamic_cast<Text*>(pfad->getThis()))) return 0;
    int ret = 0;
    Text stxt = pfad->getText();
    stxt.replace('\\', '/');
    if (stxt.positionOf('/') == stxt.getLength() - 1)
        stxt.remove(stxt.getLength() - 1);
    DIR* hdir;
    hdir = opendir(stxt.getText());
    for (dirent* entry = readdir(hdir); entry; entry = readdir(hdir))
    {
        if (entry && entry->d_name[0] != '.') ++ret;
    }
    closedir(hdir);
    return ret;
#endif
}

RCArray<Text>* File::getFileList() const // returns a list of sub-files
{
#ifdef WIN32
    if (!pfad) return 0;
    if (!FileIsDirectory(dynamic_cast<Text*>(pfad->getThis()))) return 0;
    HANDLE fHandle;
    WIN32_FIND_DATA wfd;
    Text stxt = pfad->getText();
    stxt.replace('/', '\\');
    if (stxt.positionOf('\\') == stxt.getLength() - 1)
        stxt.append("*");
    else
        stxt.append("\\*");
    fHandle = FindFirstFile(stxt.getText(), &wfd);
    FindNextFile(fHandle, &wfd);
    RCArray<Text>* ret = new RCArray<Text>();
    int count = 0;
    while (FindNextFile(fHandle, &wfd))
    {
        Text* txt = new Text(wfd.cFileName);
        ret->add(txt, count);
        ++count;
    }
    FindClose(fHandle);
    return ret;
#else
    if (!pfad) return 0;
    if (!FileIsDirectory(dynamic_cast<Text*>(pfad->getThis()))) return 0;
    Text stxt = pfad->getText();
    stxt.replace('\\', '/');
    if (stxt.positionOf('/') == stxt.getLength() - 1)
        stxt.remove(stxt.getLength() - 1);
    DIR* hdir;
    hdir = opendir(stxt.getText());
    if (hdir)
    {
        RCArray<Text>* ret = new RCArray<Text>();
        int count = 0;
        for (dirent* entry = readdir(hdir); entry; entry = readdir(hdir))
        {
            if (entry && entry->d_name[0] != '.')
            {
                ret->add(new Text(entry->d_name), count);
                ++count;
            }
        }
        closedir(hdir);
        return ret;
    }
    return 0;
#endif
}

__int64 File::getSize() const // returns the size of the file
{
    if (!pfad) return 0;
    if (gr) return gr;
    if (!stream || !isOpen())
    {
        std::fstream* stream = new std::fstream();
        stream->open(pfad->getText(), std::ios::binary | std::ios::in);
        __int64 tmp = stream->tellg();
        stream->seekg(0, std::ios::end);
        __int64 ret = stream->tellg();
        stream->seekg(tmp, std::ios::beg);
        stream->close();
        delete stream;
        __int64* size = (__int64*)&gr;
        *size = ret;
        return ret;
    }
    __int64 tmp = stream->tellg();
    stream->seekg(0, std::ios::end);
    __int64 ret = stream->tellg();
    stream->seekg(tmp, std::ios::beg);
    __int64* size = (__int64*)&gr;
    *size = ret;
    return ret;
}

Time* File::getLastChange() const // returns the date of the last modification
{
    if (!pfad) return 0;
#ifdef WIN32
    HANDLE hFile = CreateFile(pfad->getText(),
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);
    if (hFile == INVALID_HANDLE_VALUE) return 0;
    FILETIME ftCreate, ftAccess, ftWrite;
    SYSTEMTIME stUTC, stLocal;
    if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
    {
        CloseHandle(hFile);
        return 0;
    }
    CloseHandle(hFile);
    if (!FileTimeToSystemTime(&ftWrite, &stUTC)) return 0;
    if (!SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal)) return 0;
    Time* ret = new Time();
    ret->setTime(stLocal.wYear,
        stLocal.wMonth,
        stLocal.wDay,
        stLocal.wHour,
        stLocal.wMinute,
        stLocal.wSecond);
    return ret;
#else
    struct stat attrib;
    if (stat(pfad->getText(), &attrib) != 0) return 0;
    tm* clock = gmtime(&(attrib.st_mtime));
    Time* ret = new Time();
    ret->setTime(clock->tm_year + 1900,
        clock->tm_mon + 1,
        clock->tm_mday,
        clock->tm_hour,
        clock->tm_min,
        clock->tm_sec);
    return ret;
#endif
}

bool File::exists() const // checks if the file exists
{
    if (!pfad) return 0;
    return FileExists(dynamic_cast<Text*>(pfad->getThis()));
}

__int64 File::getReadPosition() const // returns the read position
{
    if (!stream) return 0;
    return stream->tellg();
}

__int64 File::getWritePosition() const // returns the write position
{
    if (!stream) return 0;
    return stream->tellp();
}

bool File::isEnd() const // checks if the end of file is reached
{
    if (!stream || stream->tellg() < 0) return 1;
    __int64 i = getSize();
    return stream->tellg() >= i;
}

Text* File::getPfad() const // returns the file path
{
    return pfad ? dynamic_cast<Text*>(pfad->getThis()) : 0;
}

Text* File::zPfad() const
{
    return pfad;
}

// File Functions
void Framework::GetFreePfad(Text* zPfad) // Searches for an unused filename
{
    Text txt = zPfad->getText();
    for (int i = 0; FileExists(txt); i++)
    {
        txt = zPfad->getText();
        txt.append(i);
    }
    zPfad->setText(txt);
}

bool Framework::FilePathCreate(Text* pfad) // Creates a file in the path
{
    bool ret = FilePathCreate(pfad->getText());
    pfad->release();
    return ret;
}

bool Framework::FileRemove(Text* pfad) // Deletes the specified file
{
    bool ret = FileRemove(pfad->getText());
    pfad->release();
    return ret;
}

bool Framework::FileRename(Text* pfad_alt, Text* pfad_neu) // Renames the file
{
    bool ret = FileRename(pfad_alt->getText(), pfad_neu->getText());
    pfad_alt->release();
    pfad_neu->release();
    return ret;
}

bool Framework::FileExists(Text* pfad) // Checks if the file exists
{
    bool ret = FileExists(pfad->getText());
    pfad->release();
    return ret;
}

bool Framework::FileIsDirectory(Text* pfad) // checks if the path is a directory
{
    bool ret = FileIsDirectory(pfad->getText());
    pfad->release();
    return ret;
}

bool Framework::FilePathCreate(const char* pfad) // Creates a file in the path
{
    Text pf = pfad;
    bool erst = 1;
#ifdef WIN32
    pf.replace("//", "\\"); // Correct path separators
    pf.replace("/", "\\");
    for (int i = 0; i < pf.countOf("\\");
        ++i) // Create each directory if it does not exist
    {
        Text* t = pf.getTeilText(0, pf.positionOf("\\", i));
        if (!t || !t->getLength())
        {
            if (t) t->release();
            continue;
        }
        if (!FileExists(dynamic_cast<Text*>(t->getThis())))
#    pragma warning(suppress : 6031)
            _mkdir(t->getText());
        t->release();
        if (pf.positionOf("\\", i) == pf.getLength() - 1) erst = 0;
    }
#else
    pf.replace("\\", "/"); // Correct path separators
    for (int i = 0; i < pf.countOf("/");
        ++i) // Create each directory if it does not exist
    {
        Text* t = pf.getTeilText(0, pf.positionOf("/", i));
        if (!t || !t->getLength())
        {
            if (t) t->release();
            continue;
        }
        if (!FileExists(dynamic_cast<Text*>(t->getThis())))
            mkdir(t->getText(), 0777);
        t->release();
        if (pf.positionOf("\\", i) == pf.getLength() - 1) erst = 0;
    }
#endif
    if (erst)
    {
        std::ofstream f(pf, std::ios::binary); // Create file
        f.close();
    }
    return FileExists(pf);
}

bool Framework::FileRemove(const char* pfad) // Deletes the specified file
{
    Text pfa = pfad;
#ifdef WIN32
    pfa.replace('\\', '/');
    bool ret = 0;
    // check if file exists
    if (!FileIsDirectory(dynamic_cast<Text*>(pfa.getThis())))
        ret = DeleteFile(pfa.getText()) == 1; // delete file
    else
    {
        ret = 1;
        File* dat = new File();
        dat->setFile(dynamic_cast<Text*>(pfa.getThis()));
        int anz = dat->getSubFileCount();
        RCArray<Text>* liste = dat->getFileList();
        for (int i = 0; i < anz; ++i)
        {
            Text* pf = new Text(pfa.getText());
            if (pf->getText()[pf->getLength() - 1] != '/') pf->append("/");
            pf->append(*liste->z(i));
            if (ret)
                ret = FileRemove(pf);
            else
                FileRemove(pf);
        }
        liste->release();
        dat->release();
        if (ret)
            ret = RemoveDirectory(pfa.getText()) == 1;
        else
            RemoveDirectory(pfa.getText());
    }
    return ret;
#else
    pfa.replace('\\', '/');
    bool ret = 0;
    // check if file exists
    if (!FileIsDirectory(dynamic_cast<Text*>(pfa.getThis())))
        ret = std::remove(pfa.getText()) == 0; // delete file
    else
    {
        ret = 1;
        File* dat = new File();
        dat->setFile(dynamic_cast<Text*>(pfa.getThis()));
        int anz = dat->getSubFileCount();
        RCArray<Text>* liste = dat->getFileList();
        for (int i = 0; i < anz; ++i)
        {
            Text* pf = new Text(pfa.getText());
            if (pf->getText()[pf->getLength() - 1] != '/') pf->append("/");
            pf->append(*liste->z(i));
            if (ret)
                ret = FileRemove(pf);
            else
                FileRemove(pf);
        }
        liste->release();
        dat->release();
        if (ret)
            ret = std::remove(pfa.getText()) == 0;
        else
            std::remove(pfa.getText());
    }
    return ret;
#endif
}

bool Framework::FileRename(
    const char* pfad_alt, const char* pfad_neu) // Renames the file
{
#ifdef WIN32
    if (pfad_alt && pfad_neu && FileExists(pfad_alt))
    {
        bool ret = 1;
        if (FileIsDirectory(pfad_alt))
        {
            if (!FileExists(pfad_neu))
            {
                Text tmp = pfad_neu;
                tmp += "/a";
                FilePathCreate(tmp);
                FileRemove(tmp);
            }
            File d;
            d.setFile(pfad_alt);
            RCArray<Text>* list = d.getFileList();
            int anz = list->getEntryCount();
            for (int i = 0; i < anz; i++)
            {
                Text pf = pfad_neu;
                pf += "/";
                pf += list->z(i)->getText();
                Text pf_a = pfad_alt;
                pf_a += "/";
                pf_a += list->z(i)->getText();
                ret |= FileRename(pf_a, pf);
            }
            d.remove();
        }
        else
        {
            if (FileExists(pfad_neu)) return 0;
        }
        ret |= MoveFile(pfad_alt, pfad_neu) == 1; // rename file
        return ret;
    }
    return 0;
#else
    if (pfad_alt && pfad_neu && FileExists(pfad_alt))
    {
        bool ret = 1;
        if (FileIsDirectory(pfad_alt))
        {
            if (!FileExists(pfad_neu))
            {
                Text tmp = pfad_neu;
                tmp += "/a";
                FilePathCreate(tmp);
                FileRemove(tmp);
            }
            File d;
            d.setFile(pfad_alt);
            RCArray<Text>* list = d.getFileList();
            int anz = list->getEntryCount();
            for (int i = 0; i < anz; i++)
            {
                Text pf = pfad_neu;
                pf += "/";
                pf += list->z(i)->getText();
                Text pf_a = pfad_alt;
                pf_a += "/";
                pf_a += list->z(i)->getText();
                ret |= FileRename(pf_a, pf);
            }
            d.remove();
        }
        else
        {
            if (FileExists(pfad_neu)) return 0;
        }
        ret |= rename(pfad_alt, pfad_neu) == 1; // rename file
        return ret;
    }
    return 0;
#endif
}

bool Framework::FileExists(const char* pfad) // Checks if the file exists
{
#ifdef WIN32
    bool ret = PathFileExists(pfad) != 0;
    return ret;
#else
    std::ifstream file(pfad);
    if (file.good()) return 1;
    return 0;
#endif
}

bool Framework::FileIsDirectory(
    const char* pfad) // checks if the path is a directory
{
#ifdef WIN32
    WIN32_FIND_DATA wfd;
    HANDLE handle = FindFirstFile(pfad, &wfd);
    if (handle == INVALID_HANDLE_VALUE) return 0;
    FindClose(handle);
    return (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
    struct stat path_stat;
    if (stat(pfad, &path_stat) != 0) return 0;
    if (S_ISDIR(path_stat.st_mode)) return 1;
    return 0;
#endif
}