/* /////////////////////////////////////////////////////////////////////////////
 * File:        unixstl_glob_sequence.h (formerly unixstl_findfile_sequence.h)
 *
 * Purpose:     glob_sequence class.
 *
 * Created:     15th January 2002
 * Updated:     10th November 2003
 *
 * Author:      Matthew Wilson, Synesis Software Pty Ltd.
 *
 * License:     (Licensed under the Synesis Software Standard Source License)
 *
 *              Copyright (C) 2002-2003, Synesis Software Pty Ltd.
 *
 *              All rights reserved.
 *
 *              www:        http://www.synesis.com.au/unixstl
 *                          http://www.unixstl.org/
 *
 *              email:      submissions@unixstl.org  for submissions
 *                          admin@unixstl.org        for other enquiries
 *
 *              Redistribution and use in source and binary forms, with or
 *              without modification, are permitted provided that the following
 *              conditions are met:
 *
 *              (i) Redistributions of source code must retain the above
 *              copyright notice and contact information, this list of
 *              conditions and the following disclaimer.
 *
 *              (ii) Any derived versions of this software (howsoever modified)
 *              remain the sole property of Synesis Software.
 *
 *              (iii) Any derived versions of this software (howsoever modified)
 *              remain subject to all these conditions.
 *
 *              (iv) Neither the name of Synesis Software nor the names of any
 *              subdivisions, employees or agents of Synesis Software, nor the
 *              names of any other contributors to this software may be used to
 *              endorse or promote products derived from this software without
 *              specific prior written permission.
 *
 *              This source code is provided by Synesis Software "as is" and any
 *              warranties, whether expressed or implied, including, but not
 *              limited to, the implied warranties of merchantability and
 *              fitness for a particular purpose are disclaimed. In no event
 *              shall the Synesis Software 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.
 *
 * ////////////////////////////////////////////////////////////////////////// */

/* NOTE: This file has been patched from the original DMD distribution to
   work with the GDC compiler.

   Modified by David Friedman, September 2004
*/


#ifndef _INCL_UNIXSTL_H_UNIXSTL_GLOB_SEQUENCE
#define _INCL_UNIXSTL_H_UNIXSTL_GLOB_SEQUENCE

#ifndef __STLSOFT_DOCUMENTATION_SKIP_SECTION
#define _UNIXSTL_VER_H_UNIXSTL_GLOB_SEQUENCE_MAJOR      2
#define _UNIXSTL_VER_H_UNIXSTL_GLOB_SEQUENCE_MINOR      1
#define _UNIXSTL_VER_H_UNIXSTL_GLOB_SEQUENCE_REVISION   4
#define _UNIXSTL_VER_H_UNIXSTL_GLOB_SEQUENCE_EDIT       46
#endif /* !__STLSOFT_DOCUMENTATION_SKIP_SECTION */

/* /////////////////////////////////////////////////////////////////////////////
 * Includes
 */

#ifndef _INCL_UNIXSTL_H_UNIXSTL
# include "unixstl.h"                   // Include the UNIXSTL root header
#endif /* !_INCL_UNIXSTL_H_UNIXSTL */
#ifndef _INCL_UNIXSTL_H_UNIXSTL_FILESYSTEM_TRAITS
# include "unixstl_filesystem_traits.h" // filesystem_traits
#endif /* !_INCL_UNIXSTL_H_UNIXSTL_FILESYSTEM_TRAITS */
#ifndef _STLSOFT_INCL_H_STLSOFT_ITERATOR
# include "stlsoft_iterator.h"
#endif /* !_STLSOFT_INCL_H_STLSOFT_ITERATOR */
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <glob.h>

/* /////////////////////////////////////////////////////////////////////////////
 * Namespace
 *
 * The UNIXSTL components are contained within the unixstl namespace. This is
 * actually an alias for stlsoft::unixstl_project,
 *
 * The definition matrix is as follows:
 *
 * _STLSOFT_NO_NAMESPACE    _UNIXSTL_NO_NAMESPACE   unixstl definition
 * ---------------------    ---------------------   -----------------
 *  not defined              not defined             = stlsoft::unixstl_project
 *  not defined              defined                 not defined
 *  defined                  not defined             unixstl
 *  defined                  defined                 not defined
 *
 */

/* No STLSoft namespaces means no UNIXSTL namespaces */
#ifdef _STLSOFT_NO_NAMESPACES
# define _UNIXSTL_NO_NAMESPACES
#endif /* _STLSOFT_NO_NAMESPACES */

/* No UNIXSTL namespaces means no unixstl namespace */
#ifdef _UNIXSTL_NO_NAMESPACES
# define _UNIXSTL_NO_NAMESPACE
#endif /* _UNIXSTL_NO_NAMESPACES */

#ifndef _UNIXSTL_NO_NAMESPACE
# ifdef _STLSOFT_NO_NAMESPACE
/* There is no stlsoft namespace, so must define ::unixstl */
namespace unixstl
{
# else
/* Define stlsoft::unixstl_project */

namespace stlsoft
{

namespace unixstl_project
{

# endif /* _STLSOFT_NO_NAMESPACE */
#endif /* !_UNIXSTL_NO_NAMESPACE */

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

/// \weakgroup libraries STLSoft Libraries
/// \brief The individual libraries

/// \weakgroup libraries_filesystem File-System Library
/// \ingroup libraries
/// \brief This library provides facilities for defining and manipulating file-system objects

/// \defgroup unixstl_filesystem_library File-System Library (UNIXSTL)
/// \ingroup UNIXSTL libraries_filesystem
/// \brief This library provides facilities for defining and manipulating UNIX file-system objects
/// @{

/* /////////////////////////////////////////////////////////////////////////////
 * Classes
 */

/// \brief STL-like readonly sequence based on the results of file-system wildcard matches
///
/// This class presents and STL-like readonly sequence interface to allow the 
/// iteration over the results of file-system wildcard matches.

class glob_sequence
{
private:
    typedef glob_sequence       class_type;
public:
    /// The char type
    typedef us_char_a_t         char_type;
    /// The value type
    typedef char_type const     *value_type;
    /// The type of the const (non-mutating) iterator
    typedef value_type          *const_iterator;
private:
//    typedef value_type          &reference;
    typedef value_type const    &const_reference;
//    typedef value_type          *pointer;
    typedef value_type const    *const_pointer;
public:
    /// The size type
    typedef us_size_t           size_type;
    /// The difference type
    typedef us_ptrdiff_t        difference_type;

    /// The type of the const (non-mutating) reverse iterator
    typedef stlsoft_ns_qual(reverse_iterator_base)  <   const_iterator
                                                    ,   value_type
                                                    ,   const_reference
                                                    ,   const_pointer
                                                    ,   difference_type
                                                    >   const_reverse_iterator;

public:
    enum
    {
            includeDots =   0x0008  /*!< Requests that dots directories be included in the returned sequence */
        ,   directories =   0x0010  /*!< Causes the search to include directories */
        ,   files       =   0x0020  /*!< Causes the search to include files */
        ,   noSort      =   0x0100  /*!< Does not sort entries */
        ,   markDirs    =   0x0200  /*!< Mark directories with a trailing path name separator */
        ,
    };

// Construction
public:
    /// \brief Constructs a sequence according to the given criteria
    ///
    /// The constructor initialises a glob_sequence instance on the given
    /// pattern with the given flags.
    ///
    /// \param pattern The pattern against which to match the file-system contents
    /// \param flags Flags to alter the behaviour of the search
    ss_explicit_k glob_sequence(char_type const *pattern, us_int_t flags = noSort)
        : m_flags(validate_flags_(flags))
    {
        m_cItems = _init(pattern);
    }

    /// \brief Constructs a sequence according to the given criteria
    ///
    /// The constructor initialises a glob_sequence instance on the given
    /// pattern with the given flags.
    ///
    /// \param directory The directory in which the pattern is located
    /// \param pattern The pattern against which to match the file-system contents
    /// \param flags Flags to alter the behaviour of the search
    glob_sequence(char_type const *directory, char_type const *pattern, us_int_t flags = noSort)
        : m_flags(validate_flags_(flags))
    {
        m_cItems = _init(directory, pattern);
    }

    // Releases any acquired resources
    ~glob_sequence() unixstl_throw_0()
    {
        if(NULL != m_base)
        {
            globfree(&m_gl);
        }
    }

// Attributes
public:
    /// Returns the number of elements in the sequence
    us_size_t size() const
    {
        return m_cItems;
    }

    /// \brief Indicates whether the search sequence is empty
    us_bool_t empty() const
    {
        return size() == 0;
    }

    /// \brief Returns the value corresponding to the given index
    ///
    /// \note In debug-mode a runtime assert is applied to enforce that the index is valid. There is <b>no</b> release-time checking on the index validity!
    value_type const operator [](size_type index) const
    {
        unixstl_message_assert("index access out of range in glob_sequence", index < m_cItems + 1);   // Has to be +1, since legitimate to take address of one-past-the-end

        return m_base[index];
    }

// Iteration
public:
    /// Begins the iteration
    ///
    /// \return An iterator representing the start of the sequence
    const_iterator  begin() const
    {
        return m_base;
    }
    /// Ends the iteration
    ///
    /// \return An iterator representing the end of the sequence
    const_iterator  end() const
    {
        return m_base + m_cItems;
    }

    /// Begins the reverse iteration
    ///
    /// \return An iterator representing the start of the reverse sequence
    const_reverse_iterator  rbegin() const
    {
        return const_reverse_iterator(end());
    }
    /// Ends the reverse iteration
    ///
    /// \return An iterator representing the end of the reverse sequence
    const_reverse_iterator  rend() const
    {
        return const_reverse_iterator(begin());
    }

// Implementation
private:
    static us_int_t validate_flags_(us_int_t flags)
    {
        if((flags & (directories | files)) == 0)
        {
            flags |= (directories | files);
        }

        if((flags & directories) == 0)
        {
            // It's more efficient to not bother doing a separate dots check if all
            // directories are being elided.
//          flags |= includeDots;
        }

        return flags;
    }

    // Returns true if pch == "" or "/" (or "\\"), false otherwise
    static us_bool_t _is_end_of_path_elements(char_type const *pch, difference_type index)
    {
        return  pch[index] == '\0' ||
                (   pch[index + 1] == '\0' &&
                    (
#if defined(_UNIXSTL_COMPILER_IS_UNKNOWN) && \
    !defined(_UNIXSTL_GLOB_SEQUENCE_NO_BACK_SLASH_TERMINATOR)
                        pch[index] == '\\' ||
#endif /* _UNIXSTL_COMPILER_IS_UNKNOWN && !_UNIXSTL_GLOB_SEQUENCE_NO_BACK_SLASH_TERMINATOR */
                        pch[index] == '/'));
    }

    static us_bool_t _is_dots(char_type const *s, us_bool_t &bTwoDots)
    {
        return  s != 0 &&
                s[0] == '.' &&
                (   (bTwoDots = false, _is_end_of_path_elements(s, 1)) ||
                    (bTwoDots = true, ( s[1] == '.' &&
                                        _is_end_of_path_elements(s, 2))));
    }

    us_int_t _init(char_type const *directory, char_type const *pattern)
    {
        us_int_t    glob_flags  =   0;
        char_type   _directory[1 + PATH_MAX];
        char_type   _pattern[1 + PATH_MAX];

        // If a directory is given, always turn it into an absolute directory
        if( NULL != directory &&
            0 != *directory)
        {
            filesystem_traits<char_type>::str_copy(_directory, directory);
            filesystem_traits<char_type>::ensure_dir_end(_directory);
            directory = _directory;
        }

        // If a directory is given, always prefix into pattern
        if( NULL != directory &&
            0 != *directory)
        {
            filesystem_traits<char_type>::str_copy(_pattern, directory);
            filesystem_traits<char_type>::str_cat(_pattern, pattern);

            pattern = _pattern;
        }

        if(m_flags & noSort)
        {
            glob_flags |= GLOB_NOSORT;
        }

        if(m_flags & markDirs)
        {
            glob_flags |= GLOB_MARK;
        }

        if((m_flags & (directories | files)) == directories)
        {
#ifdef GLOB_ONLYDIR
            glob_flags |= GLOB_ONLYDIR;
#else
#define UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS
#endif
        }

        if(0 == glob(pattern, glob_flags, NULL, &m_gl))
        {
            char_type   **base  =   m_gl.gl_pathv;
            us_int_t    cItems  =   m_gl.gl_pathc;

            if(!(m_flags & includeDots))
            {
                // Now remove the dots. If located at the start of
                // the gl buffer, then simply increment m_base to
                // be above that. If not then rearrange the base
                // two pointers such that they are there.

                us_bool_t   foundDot1   =   false;
                us_bool_t   foundDot2   =   false;
                char_type   **begin     =   base;
                char_type   **end       =   begin + cItems;

                for(; begin != end; ++begin)
                {
                    us_bool_t   bTwoDots;

                    if(_is_dots(*begin, bTwoDots))
                    {
                        if(begin != base)
                        {
                            // Swap with whatever is at base[0]
                            char_type   *t  =   *begin;

                            *begin  = *base;
                            *base   = t;
                        }

                        ++base;
                        --cItems;

                        (bTwoDots ? foundDot2 : foundDot1) = true;

                        if( foundDot1 &&
                            foundDot2)
                        {
                            break;
                        }
                    }
                }
            }

            // We should be able to trust glob() to return only directories when
            // asked, so we assume the following only needs to be done when
            // have asked for files alone
#ifdef GLOB_ONLYDIR
#ifdef UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS
            if((m_flags & (directories | files)) != (directories | files))
#else /* ? UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS */
            if((m_flags & (directories | files)) == files)
#endif /* UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS */
#else
	    if (1)
#endif
            {
                char_type   **begin     =   base;
                char_type   **end       =   begin + cItems;

                for(; begin != end; ++begin)
                {
                    // Now need to process the file, by using stat
                    struct stat st;
                    int         res;    
                    char_type   buffer[PATH_MAX];
                    char_type   *entry      =   *begin;

                    if(0 != (m_flags & markDirs))
                    {
                        filesystem_traits<char_type>::str_copy(buffer, entry);
                        filesystem_traits<char_type>::remove_dir_end(buffer);
                        entry = buffer;
                    }
                    res = stat(entry, &st);
                    
                    if(0 != res)
                    {
                        // Failed to get info from entry. Must assume it is
                        // dead, so skip it
                    }
                    else
                    {
#ifdef UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS
                        if(m_flags & directories) // Want directories
                        {
                            if(S_IFDIR == (st.st_mode & S_IFDIR))
                            {
                                continue; // A directory, so accept it
                            }
                        }
#endif /* UNIXSTL_GLOB_SEQUENCE_ULTRA_CAUTIOUS */
                        if(m_flags & files) // Want files
                        {
                            if(S_IFREG == (st.st_mode & S_IFREG))
                            {
                                continue; // A file, so accept it
                            }
                        }
                    }

                    if(begin != base)
                    {
                        // Swap with whatever is at base[0]
                        char_type   *t  =   *begin;

                        *begin  = *base;
                        *base   = t;
                    }

                    ++base;
                    --cItems;
                }
            }

            // Set m_base and m_cItems to the correct values, with
            // or without dots. m_base is cast here to remove the
            // need for const-casting throughout the rest of the
            // class
            m_base      =   const_cast<char_type const **>(base);

            return cItems;
        }
        else
        {
            m_base = NULL;

            return 0;
        }
    }

    us_int_t _init(char_type const *pattern)
    {
        return _init(NULL, pattern);
    }

// Members
private:
    us_int_t const  m_flags;
    glob_t          m_gl;
    char_type const **m_base;
    us_int_t        m_cItems;

// Not to be implemented
private:
    glob_sequence(class_type const &);
    class_type const &operator =(class_type const &);
};

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

/// @} // end of group unixstl_filesystem_library

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

#ifndef _UNIXSTL_NO_NAMESPACE
# ifdef _STLSOFT_NO_NAMESPACE
} // namespace unixstl
# else
} // namespace unixstl_project
} // namespace stlsoft
# endif /* _STLSOFT_NO_NAMESPACE */
#endif /* !_UNIXSTL_NO_NAMESPACE */

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

#endif /* !_INCL_UNIXSTL_H_UNIXSTL_GLOB_SEQUENCE */

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