#ifndef INCLUDED_BOBCAT_FILESYSTEM_
#define INCLUDED_BOBCAT_FILESYSTEM_

#include <istream>
#include <ostream>
#include <compare>
#include <chrono>

#include <bobcat/ranger>
#include <bobcat/fsoptions>

namespace FBB
{

class DateTime;

template <typename Iterator>
class Ranger;

struct FileSystem: public FS
{
                                    // sets d_ec = 0
    friend std::istream &operator>>(std::istream &in,           // opextract
                                    FileSystem &rhs); 

    friend std::ostream &operator<<(std::ostream &out,                  // .f
                                    FileSystem const &rhs);

        // note: both must exists or an exception is thrown
    friend bool operator==(FileSystem const &lhs,                       // .f
                           FileSystem const &rhs);   

    using EC            = std::error_code;
    using FileStatus    = std::filesystem::file_status;
    using Path          = std::filesystem::path;
    using Iter          = Path::iterator;
    using ConstIter     = Path::const_iterator;
    using DirEntry      = std::filesystem::directory_entry;
    using DirIter       = std::filesystem::directory_iterator;
    using RecursiveIter = std::filesystem::recursive_directory_iterator;
    using CopyOpts      = std::filesystem::copy_options;
    using Perms         = std::filesystem::perms;
    using PermOpts      = std::filesystem::perm_options;
    using FileType      = std::filesystem::file_type;
    using SystemClock   = std::chrono::system_clock;
    using FileClock     = std::chrono::file_clock;

    private:
        mutable EC *d_ec = 0;                   // 0 if no EC is used.
        Path d_path;
    
        static EC s_errorCode;
    
    public:
        FileSystem() = default;

        FileSystem(Path const &path, bool useEC = true);            // 1
        FileSystem(Path const &path, EC &ec);                       // 2

        FileSystem(Path &&tmp, bool useEC = true);                  // 3
        FileSystem(Path &&tmp, EC &ec);                             // 4

        void swap(FileSystem &other);

        void clear();                                               // .f

        template <typename Type>                                    // .f
        FileSystem &operator/=(Type const &arg);
    
        FileSystem &operator/=(FileSystem const &arg);              // .f

        template <typename Type>                                    // .f
        FileSystem &operator+=(Type const &arg);

        FileSystem &operator+=(FileSystem const &arg);              // .f


                                        // if (not fs().absolute())
                                        // or if (not fs(ec).absolute())...
        FileSystem &operator()(EC &ec = s_errorCode);               // .f
        FileSystem const &operator()(EC &ec = s_errorCode) const;   // .f
        FileSystem &noEC();                                         // .f
        FileSystem const &noEC() const;                             // .f

        static std::error_code &errorCode();                        // .f
    
        char const *c_str() const;                                  // .f
        Path const &path() const;                                   // .f
        std::string string() const;                                 // .f

        std::string extension() const;                              // .f
        FileSystem &setExtension(std::string const &ext);           // .f
        bool hasExtension() const;                                  // .f
    
        std::string filename() const;                               // .f
        FileSystem &setFilename(std::string const &newName);        // .f
        bool hasFilename() const;                                   // .f
    
        FileSystem stem() const;                                    // .f
        FileSystem parent() const;                                  // .f
                                        
        FileSystem relative() const;    // drops a starting '/'     // .f
    
        Ranger<Iter> range();                                       // .f
        Ranger<ConstIter> range() const;                            // .f
    
        FileSystem absolute() const;                                // .f
        FileSystem canonical() const;                               // .f
    
        bool isAbsolute() const;                                    // .f
        bool isRelative() const;                                    // .f
    
                                                                    // .f
        bool copy(FileSystem const &dest, FSOptions cpOptions = DEFAULT);
        bool copy(Path const &dest, FSOptions cpOptions = DEFAULT);
    
        bool mkDir() const;     // the parent must exist            // .f
        bool mkDir(Path const &reference) const;                    // .f  
        bool mkDir(FileSystem const &reference) const;              // .f
    
        bool mkDirs() const;    // constructs all subdirs           // .f
    
        static FileSystem cwd();                                    // .f
        static FileSystem cwd(EC &ec);                              // .f

        FileSystem &setCwd();                                       // 1
        static FileSystem setCwd(Path const &path);                 // 2
        static FileSystem setCwd(Path const &path, EC &ec);         // 3
    
        bool sameAs(FileSystem const &other) const; // equivalent   // .f
        bool sameAs(Path const &other) const;                       // .f
    
        bool exists() const;                                        // .f
        static bool exists(FileStatus status);                          // .f

                //throws if not existing
        uintmax_t size() const;                                     // .f
        uintmax_t count() const;       // # hard link count         // .f
    
                                                // last_write_time      
        FileClock::time_point fcModification() const;               // .f
        SystemClock::time_point modification() const;               // .f
    
        bool setModification(SystemClock::time_point const &time);  // 1.cc
        bool setModification(FBB::DateTime const &time);            // 2.cc

            // the calling object must refer to a simlink    
        FileSystem destination() const; // symlink's destination    // .f
    
        bool remove() const;                                        // .f
        std::uintmax_t removeAll() const;                           // .f
    
        bool rename(FileSystem const &newName) const;               // .f
        bool rename(Path const &newName) const;                     // .f
    
        bool resize(std::uintmax_t size) const;                     // .f
    
        static FileSystem tmpDir(bool ec = true);                   // tmpdir
        static FileSystem tmpDir(EC &ec);                           // .f
    
            // space, system_complete: maybe?
    
        FileType type(bool destination = true) const;               // .f

                                      // true/false: cf. status()
            // unclear how this works: returns fallse if the type doesn't
            // match, but nothing is changed
        bool setType(FileType type, bool destination = true);
    
        FileStatus status(bool destination = true) const;

            // Only returns false with file_status{}.
            // More useful: check FileStatus for file_type::not_found or
            //              file_type::unknown
        bool knownStatus() const;  
                // Koenig: no ns spec needed for is_*
                // bool is_WHATEVER(file_status status)
                // bool is_WHATEVER(path const path &entry [, error_code &ec])
    
                            // retrieve permissions
        Perms permissions() const;                                  // .f
    
        template <typename PermType>
        FileSystem const &setPermissions(PermType perms,            // .f
                                         FSOptions opt = RESET) const;

        template <typename PermType>
        FileSystem &setPermissions(PermType perms,                  // .f
                                   FSOptions opt = RESET);
    
        DirEntry directory() const;                                 // .f
    
            // returns directory_entry objects, only the current dir
        Ranger<DirIter> dirRange() const;                           // 1.cc

            // returns directory_iterators to all (recursive) entries
        Ranger<RecursiveIter> recursiveRange() const;               // 2.cc
        
    private:
        template <typename IterType>
        Ranger<IterType> dirRange() const;

        FileSystem &set(EC *ptr) const;     // backdoor (for op()() members)

        template <typename ReturnType>
        ReturnType optEC1(ReturnType (*fun1)(Path const &),         // .f
                          ReturnType (*fun2)(Path const &, EC &) ) const;

        bool optEC2(Path const &dest,
                    bool (*fun1)(Path const &, Path const &),       // 2.cc
                    bool (*fun2)(Path const &, Path const &, EC &) ) const;

        bool optEC3(Path const &dest,
                    void (*fun1)(Path const &, Path const &),       // 3.cc
                    void (*fun2)(Path const &, Path const &, EC &) ) const;

            // private backdoor for setPermissions
        FileSystem &setPerms(Perms perms, FSOptions opt) const;

        bool hardLink(Path const &dest) const;                      // .ih
        bool symLink(Path const &dest) const;                       // .ih
        bool cpSymLink(Path const &dest) const;                     // .ih
        bool copyFile(Path const &dest, unsigned options) const;
        bool fsCopy(Path const &dest, unsigned options) const;
};

inline std::ostream &operator<<(std::ostream &out, FileSystem const &rhs)
{
    return out << rhs.d_path.string();
}

inline bool operator==(FileSystem const &lhs, FileSystem const &rhs)
{
    return lhs.sameAs(rhs);
}

template <typename Type>
FileSystem &FileSystem::operator/=(Type const &arg)
{
    d_path /= arg;
    return *this;
}

inline FileSystem &FileSystem::operator/=(FileSystem const &arg)
{
    return *this /= arg.d_path;
}

template <typename Type>
FileSystem &FileSystem::operator+=(Type const &arg)
{
    d_path += arg;
    return *this;
}

inline FileSystem &FileSystem::operator+=(FileSystem const &arg)
{
    return *this += arg.d_path;
}

inline char const *FileSystem::c_str() const
{
    return d_path.c_str();
}

inline std::string FileSystem::extension() const
{
    return d_path.extension();
}

inline std::string FileSystem::filename() const
{
    return d_path.filename();
}

inline FileSystem::Path const &FileSystem::path() const
{
    return d_path;
}

inline FileSystem FileSystem::stem() const
{
    return d_path.stem();
}

inline std::string FileSystem::string() const
{
    return d_path.string();
}

inline bool FileSystem::isAbsolute() const
{
    return d_path.is_absolute();
}

inline bool FileSystem::hasExtension() const
{
    return d_path.has_extension();
}

inline bool FileSystem::hasFilename() const
{
    return d_path.has_filename();
}

inline bool FileSystem::isRelative() const
{
    return d_path.is_relative();
}

inline FileSystem FileSystem::parent() const
{
    return d_path.parent_path();
}

inline FileSystem FileSystem::relative() const
{
    return d_path.relative_path();
}

inline void FileSystem::clear()
{
    d_path.clear();
}

inline Ranger<FileSystem::Iter> FileSystem::range()
{
    return Ranger<Iter>{ d_path.begin(), d_path.end() };
}

inline Ranger<FileSystem::ConstIter> FileSystem::range() const
{
    return Ranger<ConstIter>{ d_path.begin(), d_path.end() };
}

inline FileSystem &FileSystem::operator()(EC &ec)
{
    return set(&ec);
}

inline FileSystem const &FileSystem::operator()(EC &ec) const
{
    return set(&ec);
}

inline FileSystem &FileSystem::noEC()
{
    return set(0);
}

inline FileSystem const &FileSystem::noEC() const
{
    return set(0);
}

inline FileSystem FileSystem::absolute() const
{
    namespace fs = std::filesystem;
    return optEC1<Path>(fs::absolute, fs::absolute);
//    return absCan(fs::absolute, fs::absolute);
}

inline FileSystem FileSystem::canonical() const
{
    namespace fs = std::filesystem;
    return optEC1<Path>(fs::canonical, fs::canonical);
//    return absCan(fs::canonical, fs::canonical);
}

inline bool FileSystem::copy(FileSystem const &dest, FSOptions cpOptions)
{
    return copy(dest.d_path, cpOptions);
}

inline bool FileSystem::mkDir() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::create_directory, fs::create_directory);
}

inline bool FileSystem::mkDir(Path const &reference) const
{
    namespace fs = std::filesystem;
    return optEC2(reference, fs::create_directory, fs::create_directory);
}

inline bool FileSystem::mkDir(FileSystem const &reference) const
{
    return mkDir(reference.d_path);
}

inline bool FileSystem::mkDirs() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::create_directories, fs::create_directories);
}

// static 
inline FileSystem FileSystem::cwd()
{
    return std::filesystem::current_path();
}

// static 
inline FileSystem FileSystem::cwd(EC &ec)
{
    return std::filesystem::current_path(ec);
}

inline bool FileSystem::sameAs(Path const &rhs) const
{
    namespace fs = std::filesystem;
    return d_ec == 0 ?
                fs::equivalent(d_path, rhs)
            :
                fs::equivalent(d_path, rhs, *d_ec);
}

inline bool FileSystem::sameAs(FileSystem const &rhs) const
{
    return sameAs(rhs.d_path);
}

inline bool FileSystem::exists() const
{
     namespace fs = std::filesystem;
    return optEC1(fs::exists, fs::exists);
}

// static
inline bool FileSystem::exists(FileStatus status)
{
    return std::filesystem::exists(status);
}

template <typename ReturnType>
inline ReturnType FileSystem::optEC1(
                            ReturnType (*fun1)(Path const &),
                            ReturnType (*fun2)(Path const &, EC &)) const
{
    return d_ec == 0 ? fun1(d_path) : fun2(d_path, *d_ec);
}

inline std::uintmax_t FileSystem::size() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::file_size, fs::file_size);
}

inline std::uintmax_t FileSystem::count() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::hard_link_count, fs::hard_link_count);
}

inline FileSystem::FileClock::time_point FileSystem::fcModification() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::last_write_time, fs::last_write_time);
}

inline FileSystem::SystemClock::time_point FileSystem::modification() const
{
    return FileClock::to_sys(fcModification());
}

inline FileSystem FileSystem::destination() const
{
    namespace fs = std::filesystem;
    return FileSystem{ optEC1(fs::read_symlink, fs::read_symlink) };
}

inline bool FileSystem::remove() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::remove, fs::remove);
}    

inline std::uintmax_t FileSystem::removeAll() const
{
    namespace fs = std::filesystem;
    return optEC1(fs::remove_all, fs::remove_all);
}    

inline bool FileSystem::rename(Path const &newName) const
{
    namespace fs = std::filesystem;
    return optEC3(newName, fs::rename, fs::rename);
}
    
inline bool FileSystem::rename(FileSystem const &newName) const
{
    return rename(newName.d_path);
}
    
// static
inline FileSystem FileSystem::tmpDir(EC &ec)
{
    return FileSystem{ std::filesystem::temp_directory_path(ec) };
}

inline FileSystem &FileSystem::setExtension(std::string const &ext)
{
    d_path.replace_extension(ext);
    return *this;
}

inline FileSystem &FileSystem::setFilename(std::string const &newName)
{
    d_path.replace_filename(newName);
    return *this;
}

inline bool FileSystem::knownStatus() const
{
    namespace fs = std::filesystem;
    return fs::status(d_path, s_errorCode) != fs::file_status{};
}

inline FileSystem::FileType FileSystem::type(bool destination) const
{
    return status(destination).type();
}

inline FileSystem::Perms FileSystem::permissions() const
{
    return status().permissions();
}

inline FileSystem::DirEntry FileSystem::directory() const
{
    namespace fs = std::filesystem;
    return fs::directory_entry(d_path);
}


template <typename PermType>
inline FileSystem const &FileSystem::setPermissions(PermType perms, 
                                                    FSOptions opt) const
{
    return setPerms(static_cast<Perms>(perms), opt);
}

template <typename PermType>
inline FileSystem &FileSystem::setPermissions(PermType perms, FSOptions opt)
{
    return setPerms(static_cast<Perms>(perms), opt);
}

// static
inline std::error_code &FileSystem::errorCode()
{
    return s_errorCode;
}

} // FBB        


#endif


