    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_DIR_NAV_KERNEl_2_
    #define DLIB_DIR_NAV_KERNEl_2_

    #ifdef DLIB_ISO_CPP_ONLY
    #error "DLIB_ISO_CPP_ONLY is defined so you can't use this OS dependent code.  Turn DLIB_ISO_CPP_ONLY off if you want to use it."
    #endif

    #include "dir_nav_kernel_abstract.h"

    #include <string>
    #include "../uintn.h"
    #include "../algs.h"

    #include <sys/types.h>
    #include <dirent.h>
    #include <libgen.h>
    #include <limits.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <chrono>

    #if !defined(__USE_LARGEFILE64 ) && !defined(_LARGEFILE64_SOURCE)
    #define stat64 stat
    #endif

    #include <vector>
    #include "../stl_checked.h"
    #include "../enable_if.h"
    #include "../queue.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // file object
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class file
        {
            /*!
                INITIAL VALUES
                    state.name        == name()
                    state.full_name   == full_name()
                    state.file_size   == size()
                    state.last_modified == last_modified()

                CONVENTION
                    state.name        == name()
                    state.full_name   == full_name()
                    state.file_size   == size()
                    state.last_modified == last_modified()

            !*/

            friend class directory;

            struct data
            {
                uint64 file_size;
                std::string name;
                std::string full_name;
                std::chrono::time_point<std::chrono::system_clock> last_modified;
            };

            void init(const std::string& name);

        public:

            struct private_constructor{};
            inline file (
                const std::string& name,
                const std::string& full_name,
                const uint64 file_size,
                const std::chrono::time_point<std::chrono::system_clock>& last_modified,
                private_constructor
            )
            {
                state.file_size = file_size;
                state.name = name;
                state.full_name = full_name;
                state.last_modified = last_modified;
            }

            class file_not_found : public error {
                public: file_not_found(const std::string& s): error(s){}
            };

            inline file (
            )
            {
                state.file_size = 0;
            }

            file (
                const std::string& name
            ) { init(name); }

            file (
                const char* name
            ) { init(name); }

            inline const std::string& name (
            ) const { return state.name; }

            inline  const std::string& full_name (
            ) const { return state.full_name; }

            inline uint64 size (
            ) const { return state.file_size; }

            inline std::chrono::time_point<std::chrono::system_clock> last_modified (
            ) const { return state.last_modified; }

            operator std::string (
            ) const { return full_name(); }

            bool operator == (
                const file& rhs
            ) const;

            bool operator != (
                const file& rhs
            ) const { return !(*this == rhs); }

            inline bool operator < (
                const file& item
            ) const { return full_name() < item.full_name(); }

            inline void swap (
                file& item
            )
            {
                exchange(state,item.state);
            }

        private:

            // member data
            data state;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // directory object
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class directory
        {
            /*!
                INITIAL VALUES
                    state.name        == name()
                    state.full_name   == full_name()

                CONVENTION
                    state.name        == name()
                    state.full_name   == full_name()
                    is_root()          == state.name.size() == 0

            !*/

            void init(const std::string& name);

        public:
            struct private_constructor{};
            inline directory (
                const std::string& name,
                const std::string& full_name,
                private_constructor
            )
            {
                state.name = name;
                state.full_name = full_name;
            }

            struct data
            {
                std::string name;
                std::string full_name;
            };

            class dir_not_found : public error {
                public: dir_not_found(const std::string& s):error(s){}
            };
            class listing_error : public error {
                public: listing_error(const std::string& s):error(s){}
            };

            inline directory (
            )
            {
            }

            directory (
                const std::string& name
            ) { init(name); }

            directory (
                const char* name
            ) { init(name); }

            static char get_separator (
            );

            template <
                typename queue_of_files
                >
            void get_files (
                queue_of_files& files
            ) const;

            template <
                typename queue_of_dirs
                >
            void get_dirs (
                queue_of_dirs& dirs
            ) const;

            std::vector<file> get_files (
            ) const
            {
                std::vector<file> temp_vector;
                get_files(temp_vector);
                return temp_vector;
            }

            std::vector<directory> get_dirs (
            ) const
            {
                std::vector<directory> temp_vector;
                get_dirs(temp_vector);
                return temp_vector;
            }

            const directory get_parent (
            ) const;

            inline bool is_root (
            ) const { return state.name.size() == 0; }

            inline const std::string& name (
            ) const { return state.name; }

            inline const std::string& full_name (
            ) const { return state.full_name; }

            operator std::string (
            ) const { return full_name(); }

            bool operator == (
                const directory& rhs
            ) const;

            bool operator != (
                const directory& rhs
            ) const { return !(*this == rhs); }

            inline bool operator < (
                const directory& item
            ) const { return full_name() < item.full_name(); }

            inline void swap (
                directory& item
            )
            {
                exchange(state,item.state);
            }

        private:

            // member data
            data state;

            bool is_root_path (
                const std::string& path
            ) const;
            /*!
                ensures
                    - returns true if path is a root path.
                      Note that this function considers root paths that don't
                      have a trailing separator to also be valid.
            !*/

        };

    // ----------------------------------------------------------------------------------------

        inline std::ostream& operator<< (
            std::ostream& out,
            const directory& item
        ) { out << (std::string)item; return out; }

        inline std::ostream& operator<< (
            std::ostream& out,
            const file& item
        ) { out << (std::string)item; return out; }

    // ----------------------------------------------------------------------------------------

        inline void swap (
            file& a,
            file& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------

        inline void swap (
            directory& a,
            directory& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // templated member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        typename disable_if<is_std_vector<queue_of_files>,void>::type
        directory_helper_get_files (
            const directory::data& state,
            queue_of_files& files
        )
        {
            files.clear();
            if (state.full_name.size() == 0)
                throw directory::listing_error("This directory object currently doesn't represent any directory.");

            DIR* ffind = 0;
            struct dirent* data;
            struct stat64 buffer;

            try
            {
                std::string path = state.full_name;
                // ensure that the path ends with a separator
                if (path[path.size()-1] != directory::get_separator())
                    path += directory::get_separator();

                // get a handle to something we can search with
                ffind = opendir(state.full_name.c_str());
                if (ffind == 0)
                {
                    throw directory::listing_error("Unable to list the contents of " + state.full_name);
                }

                while(true)
                {
                    errno = 0;
                    if ( (data = readdir(ffind)) == 0)
                    {
                        // there was an error or no more files
                        if ( errno == 0)
                        {
                            // there are no more files
                            break;
                        }
                        else
                        {
                            // there was an error
                            throw directory::listing_error("Unable to list the contents of " + state.full_name);
                        }
                    }

                    uint64 file_size;
                    // get a stat64 structure so we can see if this is a file
                    if (::stat64((path+data->d_name).c_str(), &buffer) != 0)
                    {
                        // this might be a broken symbolic link.  We can check by calling
                        // readlink and seeing if it finds anything.
                        char buf[PATH_MAX];
                        ssize_t temp = readlink((path+data->d_name).c_str(),buf,sizeof(buf));
                        if (temp == -1)
                            throw directory::listing_error("Unable to list the contents of " + state.full_name);
                        else
                            file_size = static_cast<uint64>(temp);
                    }
                    else
                    {
                        file_size = static_cast<uint64>(buffer.st_size);
                    }
                    auto last_modified = std::chrono::system_clock::from_time_t(buffer.st_mtime);
    #ifdef _BSD_SOURCE
                    last_modified += std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::nanoseconds(buffer.st_atim.tv_nsec));
    #endif

                    if (S_ISDIR(buffer.st_mode) == 0)
                    {
                        // this is actually a file
                        file temp(
                            data->d_name,
                            path+data->d_name,
                            file_size,
                            last_modified,
                            file::private_constructor()
                            );
                        files.enqueue(temp);
                    }
                } // while (true)

                if (ffind != 0)
                {
                    while (closedir(ffind))
                    {
                        if (errno != EINTR)
                            break;
                    }
                    ffind = 0;
                }

            }
            catch (...)
            {
                if (ffind != 0)
                {
                    while (closedir(ffind))
                    {
                        if (errno != EINTR)
                            break;
                    }
                    ffind = 0;
                }
                files.clear();
                throw;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        typename enable_if<is_std_vector<queue_of_files>,void>::type
        directory_helper_get_files (
            const directory::data& state,
            queue_of_files& files
        )
        {
            queue<file>::kernel_2a temp_files;
            directory_helper_get_files(state,temp_files);

            files.clear();

            // copy the queue of files into the vector
            temp_files.reset();
            while (temp_files.move_next())
            {
                files.push_back(temp_files.element());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        void directory::
        get_files (
            queue_of_files& files
        ) const
        {
            // the reason for this indirection here is because it avoids a bug in
            // the cygwin version of gcc
            directory_helper_get_files(state,files);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        typename disable_if<is_std_vector<queue_of_dirs>,void>::type
        directory_helper_get_dirs (
            const directory::data& state,
            queue_of_dirs& dirs
        )
        {
            dirs.clear();
            if (state.full_name.size() == 0)
                throw directory::listing_error("This directory object currently doesn't represent any directory.");

            DIR* ffind = 0;
            struct dirent* data;
            struct stat64 buffer;

            try
            {
                std::string path = state.full_name;
                // ensure that the path ends with a separator
                if (path[path.size()-1] != directory::get_separator())
                    path += directory::get_separator();

                // get a handle to something we can search with
                ffind = opendir(state.full_name.c_str());
                if (ffind == 0)
                {
                    throw directory::listing_error("Unable to list the contents of " + state.full_name);
                }

                while(true)
                {
                    errno = 0;
                    if ( (data = readdir(ffind)) == 0)
                    {
                        // there was an error or no more files
                        if ( errno == 0)
                        {
                            // there are no more files
                            break;
                        }
                        else
                        {
                            // there was an error
                            throw directory::listing_error("Unable to list the contents of " + state.full_name);
                        }
                    }

                    // get a stat64 structure so we can see if this is a file
                    if (::stat64((path+data->d_name).c_str(), &buffer) != 0)
                    {
                        // just assume this isn't a directory.  It is probably a broken
                        // symbolic link.
                        continue;
                    }

                    std::string dtemp(data->d_name);
                    if (S_ISDIR(buffer.st_mode) &&
                        dtemp != "." &&
                        dtemp != ".." )
                    {
                        // this is a directory so add it to dirs
                        directory temp(dtemp,path+dtemp, directory::private_constructor());
                        dirs.enqueue(temp);
                    }
                } // while (true)

                if (ffind != 0)
                {
                    while (closedir(ffind))
                    {
                        if (errno != EINTR)
                            break;
                    }
                    ffind = 0;
                }

            }
            catch (...)
            {
                if (ffind != 0)
                {
                    while (closedir(ffind))
                    {
                        if (errno != EINTR)
                            break;
                    }
                    ffind = 0;
                }
                dirs.clear();
                throw;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        typename enable_if<is_std_vector<queue_of_dirs>,void>::type
        directory_helper_get_dirs (
            const directory::data& state,
            queue_of_dirs& dirs
        )
        {
            queue<directory>::kernel_2a temp_dirs;
            directory_helper_get_dirs(state,temp_dirs);

            dirs.clear();

            // copy the queue of dirs into the vector
            temp_dirs.reset();
            while (temp_dirs.move_next())
            {
                dirs.push_back(temp_dirs.element());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        void directory::
        get_dirs (
            queue_of_dirs& dirs
        ) const
        {
            // the reason for this indirection here is because it avoids a bug in
            // the cygwin version of gcc
            directory_helper_get_dirs(state,dirs);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dir
            >
        typename disable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots (
            queue_of_dir& roots
        )
        {
            roots.clear();
            directory dir("/");
            roots.enqueue(dir);
        }

        template <
            typename queue_of_dir
            >
        typename enable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots (
            std::vector<directory>& roots
        )
        {
            roots.clear();
            directory dir("/");
            roots.push_back(dir);
        }

    // ----------------------------------------------------------------------------------------

    }

    #ifdef NO_MAKEFILE
    #include "dir_nav_kernel_2.cpp"
    #endif

    #endif // DLIB_DIR_NAV_KERNEl_2_