/*=============================================================================
    Copyright (c) 2003 Giovanni Bajo
    Copyright (c) 2003 Martin Wille
    Copyright (c) 2003 Hartmut Kaiser
    http://spirit.sourceforge.net/

    Use, modification and distribution is subject to the Boost Software
    License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
    http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

#ifndef BOOST_SPIRIT_FILE_ITERATOR_IPP
#define BOOST_SPIRIT_FILE_ITERATOR_IPP

#ifdef BOOST_SPIRIT_FILEITERATOR_WINDOWS
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>
#endif

#include <cstdio>
#include <boost/shared_ptr.hpp>

#ifdef BOOST_SPIRIT_FILEITERATOR_WINDOWS
#  include <boost/type_traits/remove_pointer.hpp>
#endif

#ifdef BOOST_SPIRIT_FILEITERATOR_POSIX
#  include <sys/types.h> // open, stat, mmap, munmap
#  include <sys/stat.h>  // stat
#  include <fcntl.h>     // open
#  include <unistd.h>    // stat, mmap, munmap
#  include <sys/mman.h>  // mmap, mmunmap
#endif

///////////////////////////////////////////////////////////////////////////////
namespace boost { namespace spirit {

///////////////////////////////////////////////////////////////////////////////
namespace fileiter_impl {

///////////////////////////////////////////////////////////////////////////////
//
//  std_file_iterator
//
//  Base class that implements iteration through a file using standard C
//  stream library (fopen and friends). This class and the following are
//  the base components on which the iterator is built (through the
//  iterator adaptor library).
//
//  The opened file stream (FILE) is held with a shared_ptr<>, whose
//  custom deleter invokes fcose(). This makes the syntax of the class
//  very easy, especially everything related to copying.
//
///////////////////////////////////////////////////////////////////////////////

template <typename CharT>
class std_file_iterator
{
public:
    typedef CharT value_type;

    std_file_iterator()
    {}

    explicit std_file_iterator(std::string fileName)
    {
        using namespace std;
        FILE* f = fopen(fileName.c_str(), "rb");

        // If the file was opened, store it into
        //  the smart pointer.
        if (f)
        {
            m_file.reset(f, fclose);
            m_pos = 0;
            m_eof = false;
            update_char();
        }
    }

    std_file_iterator(const std_file_iterator& iter)
    { *this = iter; }

    std_file_iterator& operator=(const std_file_iterator& iter)
    {
        m_file = iter.m_file;
        m_curChar = iter.m_curChar;
        m_eof = iter.m_eof;
        m_pos = iter.m_pos;

        return *this;
    }

    // Nasty bug in Comeau up to 4.3.0.1, we need explicit boolean context
    //  for shared_ptr to evaluate correctly
    operator bool() const
    { return m_file ? true : false; }

    bool operator==(const std_file_iterator& iter) const
    {
        return (m_file == iter.m_file) && (m_eof == iter.m_eof) &&
            (m_pos == iter.m_pos);
    }

    const CharT& get_cur_char(void) const
    {
        return m_curChar;
    }

    void prev_char(void)
    {
        m_pos -= sizeof(CharT);
        update_char();
    }

    void next_char(void)
    {
        m_pos += sizeof(CharT);
        update_char();
    }

    void seek_end(void)
    {
        using namespace std;
        fseek(m_file.get(), 0, SEEK_END);
        m_pos = ftell(m_file.get()) / sizeof(CharT);
        m_eof = true;
    }

    void advance(std::ptrdiff_t n)
    {
        m_pos += n * sizeof(CharT);
        update_char();
    }

    std::ptrdiff_t distance(const std_file_iterator& iter) const
    {
        return (std::ptrdiff_t)(m_pos - iter.m_pos) / sizeof(CharT);
    }

private:
    boost::shared_ptr<std::FILE> m_file;
    std::size_t m_pos;
    CharT m_curChar;
    bool m_eof;

    void update_char(void)
    {
        using namespace std;
        if ((std::size_t)ftell(m_file.get()) != m_pos)
            fseek(m_file.get(), m_pos, SEEK_SET);

        m_eof = (fread(&m_curChar, sizeof(CharT), 1, m_file.get()) < 1);
    }
};


///////////////////////////////////////////////////////////////////////////////
//
//  mmap_file_iterator
//
//  File iterator for memory mapped files, for now implemented on Windows and
//  POSIX  platforms. This class has the same interface of std_file_iterator,
//  and can be used in its place (in fact, it's the default for Windows and
//  POSIX).
//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// mmap_file_iterator, Windows version
#ifdef BOOST_SPIRIT_FILEITERATOR_WINDOWS
template <typename CharT>
class mmap_file_iterator
{
public:
    typedef CharT value_type;

    mmap_file_iterator()
    {}

    explicit mmap_file_iterator(std::string fileName)
    {
        HANDLE hFile = ::CreateFileA(
            fileName.c_str(),
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN,
            NULL
        );

        if (hFile == INVALID_HANDLE_VALUE)
            return;

        // Store the size of the file, it's used to construct
        //  the end iterator
        m_filesize = ::GetFileSize(hFile, NULL);

        HANDLE hMap = ::CreateFileMapping(
            hFile,
            NULL,
            PAGE_READONLY,
            0, 0,
            NULL
        );

        if (hMap == NULL)
        {
            ::CloseHandle(hFile);
            return;
        }

        LPVOID pMem = ::MapViewOfFile(
            hMap,
            FILE_MAP_READ,
            0, 0, 0
        );

        if (pMem == NULL)
        {
            ::CloseHandle(hMap);
            ::CloseHandle(hFile);
            return;
        }

        // We hold both the file handle and the memory pointer.
        // We can close the hMap handle now because Windows holds internally
        //  a reference to it since there is a view mapped.
        ::CloseHandle(hMap);

        // It seems like we can close the file handle as well (because
        //  a reference is hold by the filemap object).
        ::CloseHandle(hFile);

        // Store the handles inside the shared_ptr (with the custom destructors)
        m_mem.reset(static_cast<CharT*>(pMem), ::UnmapViewOfFile);

        // Start of the file
        m_curChar = m_mem.get();
    }

    mmap_file_iterator(const mmap_file_iterator& iter)
    { *this = iter; }

    mmap_file_iterator& operator=(const mmap_file_iterator& iter)
    {
        m_curChar = iter.m_curChar;
        m_mem = iter.m_mem;
        m_filesize = iter.m_filesize;

        return *this;
    }

    // Nasty bug in Comeau up to 4.3.0.1, we need explicit boolean context
    //  for shared_ptr to evaluate correctly
    operator bool() const
    { return m_mem ? true : false; }

    bool operator==(const mmap_file_iterator& iter) const
    { return m_curChar == iter.m_curChar; }

    const CharT& get_cur_char(void) const
    { return *m_curChar; }

    void next_char(void)
    { m_curChar++; }

    void prev_char(void)
    { m_curChar--; }

    void advance(std::ptrdiff_t n)
    { m_curChar += n; }

    std::ptrdiff_t distance(const mmap_file_iterator& iter) const
    { return m_curChar - iter.m_curChar; }

    void seek_end(void)
    {
        m_curChar = m_mem.get() +
            (m_filesize / sizeof(CharT));
    }

private:
#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
    typedef boost::remove_pointer<HANDLE>::type handle_t;
#else
    typedef void handle_t;
#endif

    boost::shared_ptr<CharT> m_mem;
    std::size_t m_filesize;
    CharT* m_curChar;
};

#endif // BOOST_SPIRIT_FILEITERATOR_WINDOWS

///////////////////////////////////////////////////////////////////////////////
// mmap_file_iterator, POSIX version
#ifdef BOOST_SPIRIT_FILEITERATOR_POSIX
template <typename CharT>
class mmap_file_iterator
{
private:
    struct mapping
    {
        mapping(void *p, off_t len)
            : data(p)
            , size(len)
        { }

        CharT const *begin() const
        {
            return static_cast<CharT *>(data);
        }

        CharT const *end() const
        {
            return static_cast<CharT *>(data) + size/sizeof(CharT);
        }

        ~mapping()
        {
            munmap(data, size);
        }

    private:
        void *data;
        off_t size;
    };

public:
    typedef CharT value_type;

    mmap_file_iterator()
    {}

    explicit mmap_file_iterator(std::string file_name)
    {
        // open the file
       int fd = open(file_name.c_str(),
#ifdef O_NOCTTY
            O_NOCTTY | // if stdin was closed then opening a file
                       // would cause the file to become the controlling
                       // terminal if the filename refers to a tty. Setting
                       // O_NOCTTY inhibits this.
#endif
            O_RDONLY);

        if (fd == -1)
            return;

        // call fstat to find get information about the file just
        // opened (size and file type)
        struct stat stat_buf;
        if ((fstat(fd, &stat_buf) != 0) || !S_ISREG(stat_buf.st_mode))
        {   // if fstat returns an error or if the file isn't a
            // regular file we give up.
            close(fd);
            return;
        }

        // perform the actual mapping
        void *p = mmap(0, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
        // it is safe to close() here. POSIX requires that the OS keeps a
        // second handle to the file while the file is mmapped.
        close(fd);

        if (p == MAP_FAILED)
            return;

        mapping *m = 0;
        try
        {
            m = new mapping(p, stat_buf.st_size);
        }
        catch(...)
        {
            munmap(p, stat_buf.st_size);
            throw;
        }

        m_mem.reset(m);

        // Start of the file
        m_curChar = m_mem->begin();
    }

    mmap_file_iterator(const mmap_file_iterator& iter)
    { *this = iter; }

    mmap_file_iterator& operator=(const mmap_file_iterator& iter)
    {
        m_curChar = iter.m_curChar;
        m_mem = iter.m_mem;

        return *this;
    }

    // Nasty bug in Comeau up to 4.3.0.1, we need explicit boolean context
    //  for shared_ptr to evaluate correctly
    operator bool() const
    { return m_mem ? true : false; }

    bool operator==(const mmap_file_iterator& iter) const
    { return m_curChar == iter.m_curChar; }

    const CharT& get_cur_char(void) const
    { return *m_curChar; }

    void next_char(void)
    { m_curChar++; }

    void prev_char(void)
    { m_curChar--; }

    void advance(signed long n)
    { m_curChar += n; }

    long distance(const mmap_file_iterator& iter) const
    { return m_curChar - iter.m_curChar; }

    void seek_end(void)
    {
        m_curChar = m_mem->end();
    }

private:

    boost::shared_ptr<mapping> m_mem;
    CharT const* m_curChar;
};

#endif // BOOST_SPIRIT_FILEITERATOR_POSIX

///////////////////////////////////////////////////////////////////////////////
} /* namespace boost::spirit::fileiter_impl */

template <typename CharT, typename BaseIteratorT>
file_iterator<CharT,BaseIteratorT>
file_iterator<CharT,BaseIteratorT>::make_end(void)
{
    file_iterator iter(*this);
    iter.base_reference().seek_end();
    return iter;
}

template <typename CharT, typename BaseIteratorT>
file_iterator<CharT,BaseIteratorT>&
file_iterator<CharT,BaseIteratorT>::operator=(const base_t& iter)
{
    base_t::operator=(iter);
    return *this;
}

///////////////////////////////////////////////////////////////////////////////
}} /* namespace boost::spirit */


#endif /* BOOST_SPIRIT_FILE_ITERATOR_IPP */
