
// Copyright {Jagger Software Limited} 2003

#include "io/FILE_iterator.hpp"

#include "io/auto_fsetpos.hpp"
#include "c/stdio.hpp"
#include "contract/pre_condition.hpp"
#include <cstdio>
#include <stdexcept>

using namespace ::io;
using namespace ::std;

namespace
{
   /*
    * FILE_iterator uses the long position type and relies on
    * ftell(), fseek(), fgetc() and fputc(). 
    * The C standard guarantees this works for
    * binary files < 2GB which is more than adequate for Sauce.
    *
    * All the primitive operations have been refactored into
    * this anonymous namespace in case you want
    * change the representation (perhaps to fpos_t).
    */

    template<typename arithmetic_type>
    void move_forwards(long & position, arithmetic_type amount)
    {
        position += amount;    
    }

    template<typename arithmetic_type>
    void move_backwards(long & position, arithmetic_type amount)
    {
        position -= amount;    
    }

    FILE_iterator::difference_type
    distance(long begin, long end)
    {
        return static_cast<FILE_iterator::difference_type>(end - begin);
    }

    bool equal(long lhs, long rhs)
    {
        return lhs == rhs;  
    }

    bool is_after(long lhs, long rhs)
    {
        return lhs > rhs;
    }

    bool is_before(long lhs, long rhs)
    {
        return lhs > rhs;
    }

    long getpos(FILE * stream, int mode)
    {
        ::c::fseek(stream, 0, mode);
        return ::c::ftell(stream);
    }
}

namespace io // FILE_iterator - 'tors
{
    FILE_iterator::FILE_iterator()
        : stream(0)
        , current_position(0)
        , end_position(0)
    {
    }

    FILE_iterator::FILE_iterator(FILE * target)
        : stream(target)
        , current_position(::getpos(stream, SEEK_SET))
        , end_position(::getpos(stream, SEEK_END))
    {
    }
}

namespace io // FILE_iterator - comparison
{
    // idiomatic
    
    bool operator==(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) == 0;
    }
    
    bool operator!=(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) != 0;
    }
    
    bool operator <(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) < 0;
    }
    
    bool operator<=(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) <= 0;
    }
    
    bool operator >(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) > 0;
    }
    
    bool operator>=(const FILE_iterator & lhs, const FILE_iterator & rhs)
    {
        return lhs.compare(rhs) >= 0;
    }
    
    // primitive
    
    int FILE_iterator::compare(const FILE_iterator & other) const
    {
        enum { before = -1, same = 0, after = +1 };

        if (is_end_sentinel() && other.is_end_sentinel()) 
        {
            return same; 
        }
        else if (other.is_end_sentinel()) 
        {
            return is_at_end() ? same : before;
        }
        else if (is_end_sentinel()) 
        {
            return other.is_at_end() ? same : after;
        }

        check_same_stream_as(other);

        if (is_after(current_position, other.current_position))
        {
            return after;
        }
        else if (is_before(current_position, other.current_position))
        {
            return before;
        }
        else
        {
            return same;
        }
    }
}

namespace io // FILE_iterator::reference type
{
    FILE_iterator::reference::reference(FILE * target, long where)
        : stream(target)
        , position(where)
    {
    }

    void FILE_iterator::reference::operator=(char value) 
    {
        const auto_fsetpos reset(stream, position);

        ::c::fseek(stream, position, SEEK_SET);
        ::c::fputc(value, stream);
     }

    FILE_iterator::reference::operator char() const 
    {
        const auto_fsetpos reset(stream, position);

        ::c::fseek(stream, position, SEEK_SET);
        return static_cast<char>(::c::fgetc(stream));
    }
}

namespace io // FILE_iterator - dereference
{
    FILE_iterator::reference FILE_iterator::operator*()
    {
        return reference(stream, current_position);
    }

    char FILE_iterator::operator*() const
    {
        return reference(stream, current_position);
    }
}

namespace io // FILE_iterator - subscripting
{
    FILE_iterator::reference FILE_iterator::operator[](size_type at)
    {
        const FILE_iterator moved = *this + at;
        return reference(moved.stream, moved.current_position);
    }

    char FILE_iterator::operator[](size_type at) const
    {
        return *(*this + at);
    }

    FILE_iterator::reference FILE_iterator::operator[](difference_type at)
    {
        const FILE_iterator moved = *this + at;
        return reference(moved.stream, moved.current_position);
    }

    char FILE_iterator::operator[](difference_type at) const
    {
        return *(*this + at);
    }
}

namespace io // FILE_iterator - movement
{
    FILE_iterator & FILE_iterator::operator++()
    {
        *this += 1;
        return *this;
    }

    const FILE_iterator FILE_iterator::operator++(int)
    {
        FILE_iterator old_self = *this;
        operator++();
        return old_self;
    }

    FILE_iterator & FILE_iterator::operator--()
    {
        *this -= 1;     
        return *this;
    }

    const FILE_iterator FILE_iterator::operator--(int)
    {
        FILE_iterator old_self = *this;
        operator--();
        return old_self;
    }

    /*  
     * I the following I have been very careful to
     * never change the sign of a negative value.
     */

    FILE_iterator::difference_type
    FILE_iterator::operator-(const FILE_iterator & rhs) const 
    {
        if (is_end_sentinel() && rhs.is_end_sentinel())
        {
            return 0;
        }
        else if (is_end_sentinel()) 
        {
            return rhs.distance_to_end_of_stream(); // >= 0
        }
        else if (rhs.is_end_sentinel())
        {
            return -distance_to_end_of_stream(); // < 0            
        }
        else
        {
            check_same_stream_as(rhs);
            return distance(rhs.current_position, current_position);
        }
    }

    FILE_iterator & FILE_iterator::operator+=(size_type value)
    {   
        check_not_end_sentinel();

        if (value > static_cast<size_type>(distance_to_end_of_stream()))
        {
            throw ::std::out_of_range("FILE_iterator::operator+=()");
        }

        move_forwards(current_position, value); 

        return *this;
    }

    FILE_iterator & FILE_iterator::operator-=(size_type value)
    {
        check_not_end_sentinel();
     
        if (value > static_cast<size_type>(distance_to_start_of_stream()))
        {
            throw ::std::out_of_range("FILE_iterator::operator-=()");
        }

        move_backwards(current_position, value); 

        return *this;
    }

    FILE_iterator & FILE_iterator::operator+=(difference_type value)
    {
        check_not_end_sentinel();

        if ((value > 0 && value >  distance_to_end_of_stream()) ||
            (value < 0 && value < -distance_to_start_of_stream()))
        {
            throw ::std::out_of_range("FILE_iterator::operator+=()");
        }

        move_forwards(current_position, value); 

        return *this;
    }

    FILE_iterator & FILE_iterator::operator-=(difference_type value)
    {
        check_not_end_sentinel();

        if ((value > 0 && value >  distance_to_start_of_stream()) ||
            (value < 0 && value < -distance_to_end_of_stream()))
        {
            throw ::std::out_of_range("FILE_iterator::operator-=()");
        }

        move_backwards(current_position, value); 
       
        return *this;
    }

    const FILE_iterator operator+(const FILE_iterator & lhs, 
                                  FILE_iterator::size_type rhs)
    {
        return FILE_iterator(lhs) += rhs;
    }
    
    const FILE_iterator operator+(FILE_iterator::size_type lhs, 
                                  const FILE_iterator & rhs)
    {
        return FILE_iterator(rhs) += lhs;
    }
    
    const FILE_iterator operator+(const FILE_iterator & lhs, 
                                  FILE_iterator::difference_type rhs)
    {
        return ::io::FILE_iterator(lhs) += rhs;
    }
    
    const FILE_iterator operator+(FILE_iterator::difference_type lhs, 
                                  const FILE_iterator & rhs)
    {
        return FILE_iterator(rhs) += lhs;
    }
    
    const FILE_iterator operator-(const FILE_iterator & lhs, 
                                  FILE_iterator::size_type rhs)
    {
        return FILE_iterator(lhs) -= rhs;
    }
    
    const FILE_iterator operator-(FILE_iterator::size_type lhs, 
                                  const FILE_iterator & rhs)
    {
        return FILE_iterator(rhs) -= lhs;
    }
    
    const FILE_iterator operator-(const FILE_iterator & lhs, 
                                  FILE_iterator::difference_type rhs)
    {
        return FILE_iterator(lhs) -= rhs;
    }
    
    const FILE_iterator operator-(FILE_iterator::difference_type lhs,
                                  const FILE_iterator & rhs)
    {
        return FILE_iterator(rhs) -= lhs;
    }
}
    
namespace io // FILE_iterator - implementation
{
    bool FILE_iterator::is_end_sentinel() const
    {
        return stream == 0;
    }

    bool FILE_iterator::is_at_end() const
    {
        PRE_CONDITION(!is_end_sentinel());

        return equal(current_position, end_position); 
    }

    FILE_iterator::difference_type FILE_iterator::distance_to_start_of_stream() const
    {
        PRE_CONDITION(!is_end_sentinel());

        return distance(0, current_position);
    }

    FILE_iterator::difference_type FILE_iterator::distance_to_end_of_stream() const
    {
        PRE_CONDITION(!is_end_sentinel());

        return distance(current_position, end_position);
    }
}

namespace io // FILE_iterator - validation
{
    void FILE_iterator::check_not_end_sentinel() const
    {
        if (is_end_sentinel())
        {
            throw ::std::runtime_error("FILE_iterator - at end");
        }
    }

    void FILE_iterator::check_same_stream_as(const FILE_iterator & other) const
    {
        PRE_CONDITION(!is_end_sentinel() && !other.is_end_sentinel());
 
        if (stream != other.stream)
        {
            throw ::std::runtime_error("FILE_iterator::operator - different streams");
        }
    }
}

//-------------------------------------

#ifdef FILE_ITERATOR_TEST

#include "io/FILE_iterator.hpp"
#include "ownership/scoped.hpp"
#include "ownership/fcloser.hpp"
#include <iostream>

using namespace ::io;
using namespace ::ownership;
using namespace ::std;

int main(int argc, char * argv[])
{
    scoped<fcloser, FILE*> stream(fopen(argv[1], "rb"));
    
    FILE_iterator begin(stream.get()), end;

    while (begin != end)
    {
        cout << *begin;
        ++begin;
    }

    return 0;
}

#endif


