/*
 * 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.h - 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
 * ----------------------------------------------------------------------------------
 */


#ifndef ECXX_PLATFORM_NODEMCU_FS_H
#define ECXX_PLATFORM_NODEMCU_FS_H


#include "CoreSys.h"
#include "Stream.h"


namespace eCxx {

namespace NodeMCU {


namespace FileSystem {


enum SeekMode {
    SeekSet = 0,
    SeekCur = 1,
    SeekEnd = 2
};


// Backwards compatible < 4 GB filesystems
struct FSInfo {
    size_t totalBytes;
    size_t usedBytes;
    size_t blockSize;
    size_t pageSize;
    size_t maxOpenFiles;
    size_t maxPathLength;
};


// Support > 4 GB filesystems
struct FSInfo64 {
    uint64_t totalBytes;
    uint64_t usedBytes;
    size_t   blockSize;
    size_t   pageSize;
    size_t   maxOpenFiles;
    size_t   maxPathLength;
};


class File;
class Dir;
class FS;

class FileImpl;
class DirImpl;
class FSImpl;

typedef eCxx::Ext::SharedPtr<FileImpl> FileImplPtr;
typedef eCxx::Ext::SharedPtr<DirImpl > DirImplPtr;
typedef eCxx::Ext::SharedPtr<FSImpl  > FSImplPtr;

using TimeCallback = time_t(*)();


class File : public Stream {
    public:
        inline File(FileImplPtr p = FileImplPtr(), FS* baseFS = nullptr)
        : _p(p)
        , _fakeDir(nullptr)
        , _baseFS(baseFS)
        {}

        int32_t availableForWrite() override;
        size_t write(uint8_t c) override;
        size_t write(const uint8_t *buf, size_t size) override;
        void flush() override;

        int32_t available() override;
        int32_t peek() override;
        int32_t read() override;
        size_t read(uint8_t* buf, size_t size);

        bool seek(uint32_t pos, SeekMode mode);

        virtual size_t readBytes(char* buffer, size_t length) override
        { return read( reinterpret_cast<uint8_t*>(buffer), length ); }

        inline bool seek(uint32_t pos)
        { return seek(pos, SeekSet); }

        size_t position() const;
        size_t size() const;

        void close();

        operator bool() const;

        const char* name() const;
        const char* fullName() const; // Includes path

        bool truncate(uint32_t size);

        bool isFile() const;
        bool isDirectory() const;

        // Arduino "SD class" methods for compatibility
        template<typename T>
        inline size_t write(T& src)
        {
            uint8_t obuf[256];
            size_t  doneLen = 0;
            size_t  sentLen;

            while( src.available() > sizeof(obuf) ) {
                src.read( obuf, sizeof(obuf) );
                sentLen = write( obuf, sizeof(obuf) );
                doneLen = doneLen + sentLen;
                if( sentLen != sizeof(obuf) ) return doneLen;
            }

            size_t leftLen = src.available();
            src.read(obuf, leftLen);
            sentLen = write(obuf, leftLen);
            doneLen = doneLen + sentLen;
            return doneLen;
        }

        using Print::write;

        void rewindDirectory();
        File openNextFile();

        virtual const WString readString() override;

        time_t getLastWrite();
        time_t getCreationTime();

        void setTimeCallback(TimeCallback cb);

    protected:
        FileImplPtr  _p;

        TimeCallback _timeCallback = nullptr;

        // Arduino "SD class" emulation
        eCxx::Ext::SharedPtr<Dir> _fakeDir;
        FS*                       _baseFS;
};


class Dir {
    public:
        inline Dir(DirImplPtr impl = DirImplPtr(), FS *baseFS = nullptr)
        : _impl(impl)
        , _baseFS(baseFS)
        {}

        File openFile(const char* mode);

        const WString fileName();
        size_t fileSize();
        time_t fileTime();
        time_t fileCreationTime();

        bool isFile() const;
        bool isDirectory() const;

        bool next();
        bool rewind();

        void setTimeCallback(TimeCallback cb);

    protected:
        DirImplPtr   _impl;
        FS *         _baseFS;

        TimeCallback _timeCallback = nullptr;
};


class FSConfig {
    public:
        static constexpr uint32_t FSId = 0x00000000;

        inline FSConfig(uint32_t type = FSId, bool autoFormat = true)
        : _type(type)
        , _autoFormat(autoFormat)
        {}

        inline FSConfig setAutoFormat(bool val = true)
        {
            _autoFormat = val;
            return *this;
        }

        uint32_t _type;
        bool     _autoFormat;
};


class FS {
    public:
        FS(FSImplPtr impl)
        : _impl(impl)
        { _timeCallback = _defaultTimeCB; }

        bool setConfig(const FSConfig &cfg);

        bool begin();
        void end();

        bool format();

        bool info(FSInfo& info);
        bool info64(FSInfo64& info);

        File open(const char* path, const char* mode);
        File open(const WString& path, const char* mode);

        bool exists(const char* path);
        bool exists(const WString& path);

        Dir openDir(const char* path);
        Dir openDir(const WString& path);

        bool remove(const char* path);
        bool remove(const WString& path);

        bool rename(const char* pathFrom, const char* pathTo);
        bool rename(const WString& pathFrom, const WString& pathTo);

        bool mkdir(const char* path);
        bool mkdir(const WString& path);

        bool rmdir(const char* path);
        bool rmdir(const WString& path);

        // Low-level FS routines, not needed by most applications
        bool gc();
        bool check();

        void setTimeCallback(TimeCallback cb);

    protected:
        FSImplPtr    _impl;
        TimeCallback _timeCallback = nullptr;

        FSImplPtr getImpl()
        { return _impl; }

        static time_t _defaultTimeCB()
        { return time(nullptr); }
};


} // namespace FileSystem


#ifndef FS_NO_GLOBALS
    using FileSystem::SeekMode;
    using FileSystem::File;
    using FileSystem::Dir;
    using FileSystem::FS;
    using FileSystem::FSInfo;
    using FileSystem::FSConfig;
#endif


} // namespace NodeMCU

} // namespace eCxx


//
// For compatibility with the Arduino library part
//
namespace fs = eCxx::NodeMCU::FileSystem;

typedef eCxx::NodeMCU::File File;
typedef eCxx::NodeMCU::Dir  Dir;
typedef eCxx::NodeMCU::FS   FS;


#endif
