#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------

package Common::Parser::Iterator::Text;

#
# Common::Parser::Iterator::Text
#
# An iterator class for text files.  This assumes that a
# record is stored in a single row and has a common delimeter.
#

use strict;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Util;
use Common::File::UTF16;
use Common::File::UTF8;
use Common::File::Latin1;

use base 'Common::Parser::Iterator';

###
#   Define the field delimeter
###
sub _delimeter { die "must be overloaded" }

###
#   Accessor Methods
###
sub currentRow { shift->{_currentRow} }

#sub fh         { shift->{_fh} }

# CanParse
#
# if( $iteratorClass->CanParse( filename => $file ) ) { }
#
# Can an iterator parse a specfied data file.  For text files we open the file
# and read data until we find the first non-blank line. If that line contains
# a delimeter the we can read the file.
#
sub CanParse {
    my $class = shift;
    my $self  = {};
    bless $self, $class;

    my %args      = @_;
    my $delimeter = $self->_delimeter();

    Common::Log::Debug(" - Can we parse this text file? ");
    $self->open( $args{filename} ) || return;

    # So at this point we should have a valid, open file descriptor.
    # And the first line of the file ought to be sitting in {_currentLine}.
    #
    # Now we'll use the low-level private methods to read rows from the file until
    # we encounter a line with something on it.
    # Then we'll check that for delimeters.
    # Assuming that checks out, we'll reset the file.

    Common::Log::Debug(" - Check for data");

    while ( $self->{_currentLine} ) {
        if ( !length( $self->{_currentLine} ) ) {
            $self->{_currentLine} = $self->_readNextLine();
            next;
        }

        # JPK - Why scan the whole file?
        # Just look at the first line that isn't blank.
        #
        if ( $self->{_currentLine} !~ /$delimeter/ ) {
            $self->close();
            return;
        } else {
            last;
        }
    }

    $self->close();

    return $self;
}

# open
#
# if( $iteratorClass->open( filename => $file ) ) { }
#
# Open a file for reading.  For text files we open the file and store the
# filehandle.  Then for 'next' requests we simply read a line from the file.
# This allows us to parse the file without loading the entire thing into memory.
#
sub open {
    my $self = shift;
    my $filename = shift || $self->{_filename};
    my $format;

    $self->close();

    assert( $filename,    "File required" );
    assert( -r $filename, "Can not read file: $filename" );

    Common::Log::Debug("Opening Text File: $filename");

    $self->{_fileObj} = Common::File::UTF16->new($filename);
    if ( !$self->{_fileObj}->isValid() ) {
        $self->{_fileObj} = Common::File::UTF8->new($filename);
    }
    if ( !$self->{_fileObj}->isValid() ) {
        $self->{_fileObj} = Common::File::Latin1->new($filename);
    }
    if ( !$self->{_fileObj}->isValid() ) {
        Common::Log::Print("Failed to open text file: $filename");
        $self->{_error} = "Failed to open parse file";
        return;
    }

    # Prime the iterator with the first line of data.
    #
    $self->{_currentLine} = $self->_readNextLine();

    return 1;
}

sub _readFile {
    my $self   = shift;
    my $source = shift;
    my $result;
    my $buffer;

    if ( defined( $self->{_inputFileData} ) ) {
        return $self->{_inputFileData};
    }

    if ( ( ref($source) =~ /GLOB/ ) or ( ref($source) eq 'Fh' ) ) {
        Common::Log::Debug(" - Reading from filehandle.");
        binmode($source);

        seek( $source, 0, 0 );
        while ( read( $source, $buffer, 4096 ) ) {
            $result .= $buffer;
        }
    } else {
        Common::Log::Debug(" - Creating file handle to: $source");

        # !!! Need to check the encoding of the file, and set binmodes appropriately.
        #
        # !!! I did not realize that this reads the ENTIRE FILE in to memory.
        # !!! Frankly, that's kinda stupid.
        # !!! What is the point of an iterator if we're not doing stream I/O?
        # !!! Anyway, the Common::File::* classes also seem inclined to read the whole file it
        # !!! so I guess that's convenient...
        #
        my $utf16 = Common::File::UTF16->new($source);
        my $utf8  = Common::File::UTF8->new($source);
        if ( $utf16->isValid() ) {
            my $lineArray = $utf16->read();
            $result = join( "\n", @$lineArray );
        } elsif ( $utf8->isValid() ) {
            my $lineArray = $utf8->read();
            $result = join( "\n", @$lineArray );
        } else {
            my $fh    = new IO::File($source);
            my @lines = <$fh>;
            $result = join( "\n", @lines );
        }
    }

    $self->{_inputFileData} = $result;

    return $result;
}

# close
#
# Close the open data file if open.
#
sub close {
    my $self = shift;

    $self->{_error}       = undef;
    $self->{_filename}    = undef;
    $self->{_currentLine} = undef;
    $self->{_fileObj}     = undef;
}

# rewind
#
# Move the iterator back to the begining of the file
#
sub rewind {
    my $self = shift;

    my $filename = $self->{_filename};
    $self->close();
    $self->open($filename);
}

# hasNext
#
# Are there more lines to read from the file?
#
sub hasNext {
    my $self = shift;

    return ( defined $self->{_currentLine} );
}

# next
#
# read a line from the datafile, split it up on the delimiter and return a
# Common::Parser::Item::Row object.
#
sub next {
    my $self = shift;

    return unless ( $self->hasNext() );

    # So we have a 'raw' line of data sitting in _currentLine.
    # Translate that into a Row object, then fetch the line raw line.
    #
    my $data = $self->{_currentLine};
    $self->{_currentLine} = $self->_readNextLine();

    my $delimeter = $self->_delimeter();
    my @row = split( /$delimeter/, $data );

    my $dataClass = $self->_itemClass();
    eval "require $dataClass";

    return $dataClass->new( row => \@row );
}

sub _readNextLine {
    my ($self) = @_;

    return unless $self->{_fileObj};
    return $self->{_fileObj}->readLine();
}

#sub header {
#	my $self = shift;
#    return $self->{_rowData}->[0] if( $self->{_rowData} );
#}

# _fetchRow
#
# Read a line from the input file and split it. Call from the 'next' method.
sub _fetchRow {
    my $self      = shift;
    my $data      = $self->{_rowData} || return;
    my $delimeter = $self->_delimeter();

    my $line = $data->[ $self->{_currentRow} ];

    my @row = split( /$delimeter/, $line ) if ($line);

    for ( my $i = 0 ; $i < @row ; $i++ ) {
        $row[$i] = $self->_clean( $row[$i] );
    }

    return @row;
}

###
1;    # Play nicely.
###
