/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#ifndef GEOGRAM_BASIC_FILE_SYSTEM
#define GEOGRAM_BASIC_FILE_SYSTEM

#include <geogram/basic/common.h>
#include <geogram/basic/numeric.h>
#include <geogram/basic/counted.h>
#include <geogram/basic/smart_pointer.h>

#include <string>
#include <vector>
#include <map>

/**
 * \file geogram/basic/file_system.h
 * \brief Functions and times for filesystem manipulation
 */

namespace GEO {

    /**
     * \brief Abstraction layer for file-system management.
     */
    namespace FileSystem {

        /**
         * \brief A Node in a FileSystem.
         * \details This class abstracts a FileSystem and
         *  operations on it.
         */
        class GEOGRAM_API Node : public Counted {
        public:
            /**
             * \brief Node constructor.
             */
            Node();

            /**
             * \brief Node destructor.
             */
            ~Node() override;

            /************************** OS-dependent **************************/

            /**
             * \brief Checks if a path is a regular file.
             * \param[in] path system path to verify.
             * \retval true if \p path is a regular file.
             * \retval false otherwise.
             */
            virtual bool is_file(const std::string& path);

            /**
             * \brief Checks if a path is a directory.
             * \param[in] path system path to verify.
             * \retval true if \p path is a directory.
             * \retval false otherwise.
             */
            virtual bool is_directory(const std::string& path);

            /**
             * \brief Creates a directory
             * \details This recursively creates a new directory given by its \b
             * absolute path \p path, creating any missing intermediate
             * directories on the fly.
             * \param[in] path absolute path to the directory to be created.
             * \retval true if the directory was successfully created.
             * \retval false otherwise.
             */
            virtual bool create_directory(const std::string& path);

            /**
             * \brief Deletes a directory
             * \details This deletes the directory specified by path \p path.
             *  The path must specify an empty directory.
             * \param[in] path the path of the directory to be removed.
             * \retval true if the directory was successfully deleted.
             * \retval false otherwise.
             */
            virtual bool delete_directory(const std::string& path);

            /**
             * \brief Deletes a file
             * \param[in] path the path of the file to be deleted.
             * \retval true if the file path was successfully deleted
             * \retval false otherwise
             */
            virtual bool delete_file(const std::string& path);

            /**
             * \brief Lists directory contents
             * \details Lists all the files and sub-directories in the directory
             * specified by \p path, and stores the list in \p result. Special
             * entries "." and ".." are not stored in \p result.
             * \param[in] path the path to the directory to list.
             * \param[in] result output vector of files and sub-directories.
             * \retval true if \p path specifies a readable directory.
             * \retval false otherwise.
             */
            virtual bool get_directory_entries(
                const std::string& path, std::vector<std::string>& result
            );

            /**
             * \brief Gets the current working directory.
             * \return The absolute path to the current directory.
             */
            virtual std::string get_current_working_directory();

            /**
             * \brief Sets the working directory.
             * \param[in] path path to the new working directory.
             * \retval true if the current directory could be
             *  changed to \p path.
             * \retval false otherwise.
             */
            virtual bool set_current_working_directory(
                const std::string& path
            );

            /**
             * \brief Renames or moves a file.
             * \details This renames the existing file or directory specified by
             * path \p old_name to the new path \p new_name. The new name
             * must not be the name of an existing file or directory.
             * If \p old_name and \p new_name are not in the same directory,
             *  \p old_name is moved to the \p new_name.
             * \param[in] old_name path of the file or directory to be renamed.
             * \param[in] new_name new path of the file or directory.
             * \retval true if the file was renamed successfully.
             * \retval false otherwise.
             */
            virtual bool rename_file(
                const std::string& old_name, const std::string& new_name
            );

            /**
             * \brief Gets a file last modification time.
             * \param[in] path the path to an existing file or directory.
             * \return the last modification time in seconds
             */
            virtual Numeric::uint64 get_time_stamp(const std::string& path);

            /**
             * \brief Marks a filename as executable.
             * \details On unix, it chmods the file, on Windows, does nothing.
             * \param[in] filename name of the file to be made executable
             * \retval true on success.
             * \retval false otherwise.
             */
            virtual bool set_executable_flag(const std::string& filename);


            /**
             * \brief Modifies the last modification time of a file.
             * \param[in] filename name of the file.
             * \retval true on success.
             * \retval false otherwise.
             */
            virtual bool touch(const std::string& filename);

            /**
             * \brief Normalizes a path.
             * \details A path is normalized if it is absolute and it does not
             *  contain any "../" component.
             * \param[in] path the path to be normalized. The path can have
             *  components that do not exist.
             * \return the normalized path
             */
            virtual std::string normalized_path(const std::string& path);


            /**
             * \brief Gets the current user's home directory.
             * \return The path to the current user's home directory
             *  as a string.
             */
            virtual std::string home_directory();

            /**
             * \brief Gets the current user's home directory.
             * \details Under unix, it returns the content of the HOME
             *  environment
             *  variable. Under Windows, it returns the "My Documents"
             *  directory.
             * \return The path to the current user's home directory
             *  as a string.
             */
            virtual std::string documents_directory();


            /**
             * \brief Load file contents in a string.
             * \param[in] path the path to the file
             * \return a string with the contents of the file.
             */
            virtual std::string load_file_as_string(const std::string& path);

            /************************ OS-independent **************************/

            /**
             * \brief Gets a path extension
             * \details Extracts the extension from the path \p path,
             * that is any character that appear after the last dot (.)
             * and after any
             * directory separator character. If \p path has no extension, the
             * empty string is returned.
             *
             * Examples
             * - extension("/dir/file.cpp") -> "cpp"
             * - extension("file") -> ""
             * - extension("/dir.ext/file") -> ""
             *
             * \param[in] path the path to a file or directory
             * \return the path's extension (without the dot), or the empty
             * string if none.
             */
            virtual std::string extension(const std::string& path);

            /**
             * \brief Gets a path base name
             * \details Extracts the base name from the path \p path,
             * that is any
             * character that appear after the last directory separator. If
             * parameter \p remove_extension is \c true (the default), the
             * extension is removed from the base name, otherwise is it kept. If
             * the path does not contain any directory separator, the whole path
             * is returned.
             *
             * Examples
             * - base_name("/dir/file.cpp") -> "file"
             * - base_name("/dir/file.cpp", false) -> "file.cpp"
             * - base_name("file") -> "file"
             *
             * \param[in] path the path to a file or directory
             * \param[in] remove_extension whether to remove the extension from
             * the base name or not.
             */
            virtual std::string base_name(
                const std::string& path, bool remove_extension = true
            );

            /**
             * \brief Gets a path directory
             * \details Extracts the directory from the path \p path,
             *  that is any character that appear before the last directory
             *  separator. If the path does not contain any directory
             *  separator, string "." is returned.
             *
             * Examples
             * - dir_name("/dir/file.cpp") -> "dir"
             * - dir_name("file") -> "."
             * - dir_name("/") -> "/"
             *
             * \param[in] path the path to a file or directory
             * \return the path directory or "." if none
             */
            virtual std::string dir_name(const std::string& path);

            /**
             * \brief Lists directory contents
             * \details Lists all the files and sub-directories in the directory
             * specified by \p path, and stores the list in \p result. Special
             * entries "." and ".." are not stored in \p result. If parameter
             * recursive is set to \c true, \p result will include the entries
             * of all sub-directories in \p path recursively.
             * \param[in] path the path to an existing directory
             * \param[in] result output vector of entries in \p path
             * \param[in] recursive recursively traverses all sub-directories in
             * \p path
             */
            virtual void get_directory_entries(
                const std::string& path,
                std::vector<std::string>& result, bool recursive
            );

            /**
             * \brief Lists files in a directory
             * \details Lists all the files in the directory specified by
             *  \p path, and stores the list in \p result. Special entries "."
             *  and ".." are not stored in \p result. If parameter recursive
             *  is set to \c true, \p result will include the entries of all
             *  sub-directories in \p path recursively.
             * \param[in] path the path to an existing directory
             * \param[in] result output vector of files in \p path
             * \param[in] recursive recursively traverses all sub-directories in
             * \p path
             * \see get_directory_entries()
             */
            virtual void get_files(
                const std::string& path,
                std::vector<std::string>& result, bool recursive = false
            );

            /**
             * \brief Lists sub-directories in a directory
             * \details Lists all the sub-directories in the directory specified
             * by \p path, and stores the list in \p result. Special entries "."
             * and ".." are not stored in \p result. If parameter recursive
             * is set to \c true, \p result will include the entries of all
             * sub-directories in \p path recursively.
             * \param[in] path the path to an existing directory
             * \param[in] result output vector of sub-directories in \p path
             * \param[in] recursive recursively traverses all sub-directories in
             * \p path
             * \see get_directory_entries()
             */
            virtual void get_subdirectories(
                const std::string& path,
                std::vector<std::string>& result, bool recursive = false
            );

            /**
             * \brief Converts a path to Unix format
             * \details It changes all Windows "\" directory separators into
             *  Unix "/" directory separators.
             * \param[in,out] path the path to be converted
             */
            virtual void flip_slashes(std::string& path);

            /**
             * \brief Copies a file
             * \param[in] from name of the file to be copied
             * \param[out] to name of the copy
             * \retval true if the copy was successful
             * \retval false otherwise
             */
            virtual bool copy_file(
                const std::string& from, const std::string& to
            );
        };

        /**
         * \brief Implementation of a file system stored in memory.
         */
        class GEOGRAM_API MemoryNode : public Node {
        public:

            /**
             * \brief MemoryNode constructor.
             * \param[in] path full path to this node.
             */
            MemoryNode(const std::string& path="/") : path_(path) {
            }

            /** \copydoc Node::copy_file() */
            bool copy_file(
                const std::string& from, const std::string& to
            ) override ;

            /** \copydoc Node::load_file_as_string() */
            std::string load_file_as_string(const std::string& path) override;

            /** \copydoc Node::is_file() */
            virtual bool is_file(const std::string& path) override;

            /** \copydoc Node::is_directory() */
            virtual bool is_directory(const std::string& path) override;

            /** \copydoc Node::create_directory() */
            virtual bool create_directory(const std::string& path) override;

            /** \copydoc Node::delete_directory() */
            virtual bool delete_directory(const std::string& path) override;

            /** \copydoc Node::delete_file() */
            virtual bool delete_file(const std::string& path) override;

            /** \copydoc Node::get_directory_entries() */
            bool get_directory_entries(
                const std::string& path, std::vector<std::string>& result
            ) override;


            /** \copydoc Node::rename_file() */
            bool rename_file(
                const std::string& old_name, const std::string& new_name
            ) override;

            /**
             * \brief Gets the contents of a file.
             * \param[in] path the path to the file.
             * \return a const pointer to the contents of the file.
             */
            const char* get_file_contents(const std::string& path);

            /**
             * \brief Creates a file.
             * \param[in] path the path to the file
             * \param[in] content a const pointer to the contents of the file
             */
            bool create_file(const std::string& path, const char* content);

        protected:
            /**
             * \brief Splits a path.
             * \param[in] path the path
             * \param[out] leadingsubdir the leading subdirectory or the
             *  empty string
             * \param[out] rest the rest of the path
             */
            static void split_path(
                const std::string& path, std::string& leadingsubdir,
                std::string& rest
            );

        private:
            std::string path_;
            std::map<std::string, SmartPointer<MemoryNode> > subnodes_;
            std::map<std::string, const char*> files_;
        };

        typedef SmartPointer<Node> Node_var;

        /**********************************************************/

        /**
         * \brief Initializes the FileSystem library.
         * \details This function is automatically called during
         *  Geogram startup. It should not be called by client
         *  code.
         */
        void GEOGRAM_API initialize();

        /**
         * \brief Terminates the FileSystem library.
         * \details This function is automatically called during
         *  Geogram shutdown. It should not be called by client
         *  code.
         */
        void GEOGRAM_API terminate();

        /** \copydoc FileSystem::Node::is_file() */
        bool GEOGRAM_API is_file(const std::string& path);

        /** \copydoc FileSystem::Node::is_directory() */
        bool GEOGRAM_API is_directory(const std::string& path);

        /**
         * \brief Tests whether one can read files from a directory.
         * \param[in] path the directory to be tested, either
         *  absolute or relative to current working directory.
         * \retval true if one can read files in \p path
         * \retval false otherwise
         */
        bool GEOGRAM_API can_read_directory(const std::string& path);

        /**
         * \brief Tests whether a directory can be written to.
         * \param[in] path the directory to be tested, either
         *  absolute or relative to current working directory.
         * \param[in] create_missing_directories if set, then
         *  the function tentatively creates the subdirectories
         *  that do not already exist (as "mkdir -p" does).
         */
        bool GEOGRAM_API can_write_directory(
            const std::string& path, bool create_missing_directories = false
        );

        /** \copydoc FileSystem::Node::create_directory() */
        bool GEOGRAM_API create_directory(const std::string& path);

        /** \copydoc FileSystem::Node::delete_directory() */
        bool GEOGRAM_API delete_directory(const std::string& path);

        /** \copydoc FileSystem::Node::delete_file() */
        bool GEOGRAM_API delete_file(const std::string& path);

        /** \copydoc FileSystem::Node::get_directory_entries() */
        bool GEOGRAM_API get_directory_entries(
            const std::string& path, std::vector<std::string>& result
        );

        /** \copydoc FileSystem::Node::get_current_working_directory() */
        std::string GEOGRAM_API get_current_working_directory();
        bool GEOGRAM_API set_current_working_directory(
            const std::string& path
        );

        /** \copydoc FileSystem::Node::rename_file() */
        bool GEOGRAM_API rename_file(
            const std::string& old_name, const std::string& new_name
        );

        /** \copydoc FileSystem::Node::get_time_stamp() */
        Numeric::uint64 GEOGRAM_API get_time_stamp(
            const std::string& path
        );

        /** \copydoc FileSystem::Node::extension() */
        std::string GEOGRAM_API extension(const std::string& path);

        /** \copydoc FileSystem::Node::base_name() */
        std::string GEOGRAM_API base_name(
            const std::string& path, bool remove_extension = true
        );

        /** \copydoc FileSystem::Node::dir_name() */
        std::string GEOGRAM_API dir_name(const std::string& path);

        /** \copydoc FileSystem::Node::get_directory_entries() */
        void GEOGRAM_API get_directory_entries(
            const std::string& path,
            std::vector<std::string>& result, bool recursive
        );

        /** \copydoc FileSystem::Node::get_files() */
        void GEOGRAM_API get_files(
            const std::string& path,
            std::vector<std::string>& result, bool recursive = false
        );

        /** \copydoc FileSystem::Node::get_subdirectories() */
        void GEOGRAM_API get_subdirectories(
            const std::string& path,
            std::vector<std::string>& result, bool recursive = false
        );

        /** \copydoc FileSystem::Node::flip_slashes() */
        void GEOGRAM_API flip_slashes(std::string& path);

        /** \copydoc FileSystem::Node::copy_file() */
        bool GEOGRAM_API copy_file(
            const std::string& from, const std::string& to
        );

        /** \copydoc FileSystem::Node::set_executable_flag() */
        bool GEOGRAM_API set_executable_flag(const std::string& filename);

        /** \copydoc FileSystem::Node::touch() */
        bool GEOGRAM_API touch(const std::string& filename);

        /** \copydoc FileSystem::Node::normalized_path() */
        std::string GEOGRAM_API normalized_path(const std::string& path);

        /** \copydoc FileSystem::Node::absolute_path() */
        std::string GEOGRAM_API absolute_path(const std::string& path);

        /** \copydoc FileSystem::Node::home_directory() */
        std::string GEOGRAM_API home_directory();

        /** \copydoc FileSystem::Node::documents_directory() */
        std::string GEOGRAM_API documents_directory();

        /**
         * \brief Gets the root of the file system.
         * \param[out] root a pointer to the root of
         *  the FileSystem.
         */
        void GEOGRAM_API get_root(Node*& root);

#ifdef GEO_OS_EMSCRIPTEN
        /**
         * \brief Declares a function to be called whenever the file system
         *  changes.
         * \details The function will be called when the user loads a file
         *  using the button in the webpage.
         * \param[in] callback the function to be called.
         */
        void set_file_system_changed_callback(void(*callback)());
#endif

    }
}

#endif
