/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a slight modifications) from:
 *     FS.cpp - file system wrapper
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2015 Ivan Grokhotkov. All rights reserved.
 *
 * This file is part of the esp8266 core for Arduino environment.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include "FSImpl.h"


namespace eCxx {

namespace NodeMCU {


using namespace FileSystem;


static bool sflags(const char* mode, OpenMode& om, AccessMode& am)
{
    switch( mode[0] ) {
        case 'r':
            am = AM_READ;
            om = OM_DEFAULT;
            break;
        case 'w':
            am = AM_WRITE;
            om = static_cast<OpenMode>(OM_CREATE | OM_TRUNCATE);
            break;
        case 'a':
            am = AM_WRITE;
            om = static_cast<OpenMode>(OM_CREATE | OM_APPEND);
            break;
        default:
            return false;
    }

    switch( mode[1] ) {
        case '+':
            am = static_cast<AccessMode>(AM_WRITE | AM_READ);
            break;
        case 0:
            break;
        default:
            return false;
    }

    return true;
}


/////////////////////////////////////////////////////////////////////////////////////


int32_t File::availableForWrite()
{
    if(!_p) return 0;
    return _p->availableForWrite();
}


size_t File::write(uint8_t c)
{
    if(!_p) return 0;
    return _p->write(&c, 1);
}


size_t File::write(const uint8_t* buf, size_t size)
{
    if(!_p) return 0;
    return _p->write(buf, size);
}


void File::flush()
{
    if(!_p) return;
    _p->flush();
}


int32_t File::available()
{
    if(!_p) return 0;
    return _p->size() - _p->position();
}


int32_t File::peek()
{
    if(!_p) return -1;

    const size_t  curPos = _p->position();
    const int32_t result = read();

    seek(curPos, SeekSet);

    return result;
}


int32_t File::read()
{
    if(!_p) return -1;

    uint8_t result;
    if( _p->read(&result, 1) != 1 ) return -1;

    return result;
}


size_t File::read(uint8_t* buf, size_t size)
{
    if(!_p) return 0;
    return _p->read(buf, size);
}


bool File::seek(uint32_t pos, SeekMode mode)
{
    if(!_p) return false;
    return _p->seek(pos, mode);
}


size_t File::position() const
{
    if(!_p) return 0;
    return _p->position();
}


size_t File::size() const
{
    if(!_p) return 0;
    return _p->size();
}


void File::close()
{
    if(!_p) return;

    _p->close();
    _p = nullptr;
}


File::operator bool() const
{ return !!_p; }


const char* File::name() const
 {
    if(!_p) return nullptr;
    return _p->name();
}


const char* File::fullName() const
{
    if(!_p) return nullptr;
    return _p->fullName();
}


bool File::truncate(uint32_t size)
{
    if(!_p) return false;
    return _p->truncate(size);
}


bool File::isFile() const
{
    if(!_p) return false;
    return _p->isFile();
}


bool File::isDirectory() const
{
    if(!_p) return false;
    return _p->isDirectory();
}


void File::rewindDirectory()
{
    if(!_fakeDir) {
        _fakeDir = eCxx::Ext::makeSharedPtr<Dir>( _baseFS->openDir( fullName() ) );
    }
    else {
        _fakeDir->rewind();
    }
}


File File::openNextFile()
{
    if(!_fakeDir) {
        _fakeDir = eCxx::Ext::makeSharedPtr<Dir>( _baseFS->openDir( fullName() ) );
    }

    _fakeDir->next();
    return _fakeDir->openFile("r");
}


const WString File::readString()
{
    WString ret;
    ret.reserve( size() - position() );

    char    tmp[256 + 1];
    int32_t countRead = readBytes( tmp, sizeof(tmp) - 1 );

    while(countRead > 0) {
        tmp[countRead]  = 0;
        ret            += tmp;
        countRead       = readBytes( tmp, sizeof(tmp) - 1 );
    }

    return ret;
}


time_t File::getLastWrite()
{
    if(!_p) return 0;
    return _p->getLastWrite();
}


time_t File::getCreationTime()
{
    if(!_p) return 0;
    return _p->getCreationTime();
}


void File::setTimeCallback(TimeCallback cb)
{
    if(!_p) return;
    _p->setTimeCallback(cb);

    _timeCallback = cb;
}


/////////////////////////////////////////////////////////////////////////////////////


File Dir::openFile(const char* mode)
{
    if(!_impl) return File();

    OpenMode   om;
    AccessMode am;

    if( !sflags(mode, om, am) ) {
        DEBUGV("openFile() invalid mode `%s`\r\n", mode);
        return File();
    }

    File f(_impl->openFile(om, am), _baseFS);
    f.setTimeCallback(_timeCallback);

    return f;
}


const WString Dir::fileName()
{
    if(!_impl) return emptyString;
    return _impl->fileName();
}


size_t Dir::fileSize()
{
    if(!_impl) return 0;
    return _impl->fileSize();
}


time_t Dir::fileTime()
{
    if(!_impl) return 0;
    return _impl->fileTime();
}


time_t Dir::fileCreationTime()
{
    if(!_impl) return 0;
    return _impl->fileCreationTime();
}


bool Dir::isFile() const
{
    if(!_impl) return false;
    return _impl->isFile();
}


bool Dir::isDirectory() const
{
    if(!_impl) return false;
    return _impl->isDirectory();
}


bool Dir::next()
{
    if(!_impl) return false;
    return _impl->next();
}


bool Dir::rewind()
{
    if(!_impl) return false;
    return _impl->rewind();
}


void Dir::setTimeCallback(TimeCallback cb)
{
    if(!_impl) return;
    _impl->setTimeCallback(cb);

    _timeCallback = cb;
}


/////////////////////////////////////////////////////////////////////////////////////


bool FS::setConfig(const FSConfig& cfg)
{
    if(!_impl) return false;
    return _impl->setConfig(cfg);
}


bool FS::begin()
{
    if(!_impl) {
        DEBUGV("begin() FS no implementation");
        return false;
    }

    _impl->setTimeCallback(_timeCallback);

    const bool ret = _impl->begin();

    DEBUGV("begin() %s\n", ret ? "" : "FS could not start");
    return ret;
}


void FS::end()
{ if(_impl) _impl->end(); }


bool FS::gc()
{
    if(!_impl) return false;
    return _impl->gc();
}


bool FS::check()
{
    if(!_impl) return false;
    return _impl->check();
}


bool FS::format()
{
    if(!_impl) return false;
    return _impl->format();
}


bool FS::info(FSInfo& info)
{
    if(_impl) return false;
    return _impl->info(info);
}


bool FS::info64(FSInfo64& info)
{
    if(!_impl) return false;
    return _impl->info64(info);
}


File FS::open(const char* path, const char* mode)
{
    if(!_impl) return File();

    OpenMode   om;
    AccessMode am;

    if( !sflags(mode, om, am) ) {
        DEBUGV("open() invalid mode `%s`\r\n", mode);
        return File();
    }

    File f(_impl->open(path, om, am), this);
    f.setTimeCallback(_timeCallback);

    return f;
}


File FS::open(const WString& path, const char* mode)
{ return open(path.c_str(), mode); }


bool FS::exists(const char* path)
{
    if(!_impl) return false;
    return _impl->exists(path);
}


bool FS::exists(const WString& path)
{ return exists( path.c_str() ); }


Dir FS::openDir(const char* path)
{
    if(!_impl) return Dir();

    DirImplPtr p = _impl->openDir(path);
    Dir        d(p, this);

    d.setTimeCallback(_timeCallback);

    return d;
}


Dir FS::openDir(const WString& path)
{ return openDir( path.c_str() ); }


bool FS::remove(const char* path)
{
    if(!_impl) return false;
    return _impl->remove(path);
}


bool FS::remove(const WString& path)
{ return remove( path.c_str() ); }

bool FS::rename(const char* pathFrom, const char* pathTo)
{
    if(!_impl) return false;
    return _impl->rename(pathFrom, pathTo);
}


bool FS::rename(const WString& pathFrom, const WString& pathTo)
{ return rename( pathFrom.c_str(), pathTo.c_str() ); }


bool FS::rmdir(const char* path)
{
    if(!_impl) return false;
    return _impl->rmdir(path);
}


bool FS::rmdir(const WString& path)
{ return rmdir( path.c_str() ); }


bool FS::mkdir(const char* path)
{
    if(!_impl) return false;
    return _impl->mkdir(path);
}


bool FS::mkdir(const WString& path)
{ return mkdir( path.c_str() ); }


void FS::setTimeCallback(TimeCallback cb)
{
    if(!_impl) return;
    _impl->setTimeCallback(cb);

    _timeCallback = cb;
}


} // namespace NodeMCU

} // namespace eCxx

