package RDAS::Parser::TreeBuilder;

use Carp;
use HTML::TreeBuilder::XPath;
use HTML::Tree;
use Encode;

#use encoding 'utf8';
use utf8;

use lib '/app/tools/rdas/lib';
use base 'RDAS::Parser';

use lib '/app/tools/common/lib';
use Common::Assert;

use Data::Dumper;

sub parser {
    my $self = shift;

    return $self->{_parser} if ( $self->{_parser} );

    my $content = $self->content();
    Common::Assert::assert($content);

    $self->{_parser} = HTML::Tree->new();
    $self->{_parser}->parse($content);

    return $self->{_parser};
}

sub DESTROY {
    my $self = shift;

    if ( $self->{_parser} ) {
        Log->debug("Clearing Parser");
        $self->{_parser}->delete() if ( $self->{_parser} );
        $self->{_parser} = undef;

    }
}

sub parse {
    my $self = shift;

    foreach my $field ( $self->_fieldList() ) {
        $self->$field();
    }
}

sub debugFields {
    my $self = shift;

    foreach my $field ( $self->_fieldList() ) {
        my $value = ref( $self->$field ) ? $self->$field->access() : $self->$field;
        Log->info( sprintf( "%s: %s\n", $field, defined($value) ? $value : '<undef>' ) );
    }
}

sub AUTOLOAD {
    my $self           = shift;
    my $fieldSpecifier = $AUTOLOAD;
    $fieldSpecifier =~ s/.*://;    # Strips fully-qualified portion.

    return if 'DESTROY' eq $fieldSpecifier;

    confess("no reference") if ( !ref($self) );

    if ( grep( /^$fieldSpecifier$/, $self->_validOptions() ) ) {
        return $self->{"_$fieldSpecifier"};
    } elsif ( grep( /^$fieldSpecifier$/, $self->_fieldList() ) ) {
        return $self->_getValue( name => $fieldSpecifier );
    } else {
        confess "unknown property/method \"$fieldSpecifier\"";
    }
}

sub _getValue {
    my $self = shift;
    my %args = @_;
    my $object;

    my $name = $args{name} || confess;

    my $config = $self->_getConfig($name) || die "'$name' not configured";

    my $filter    = $config->{filter};
    my $container = $config->{container};

    delete( $config->{filter} );
    delete( $config->{container} );
    delete( $config->{required} );

    return $self->{$name} if ( $self->{$name} );

    my ( $value, $token ) = $self->_getValueByTag(%$config);

    if ($container) {
        eval "require $container";
        die $@ if ($@);

        $object = $container->new( value => $value, token => $token );
        $value = $object->value();
    }

    $value = $filter->($value) if ( $value && $filter );

    $self->{$name} = $value;

    return $self->{$name};
}

sub _getConfig {
    my $self = shift;
    my $name = shift || confess;

    return $self->_fieldConfig()->{$name};
}

sub _fieldParentConfig { }

sub _fieldConfig { confess "Must be overloaded" }

sub _fieldList {
    my $self   = shift;
    my $config = $self->_fieldConfig();

    return keys(%$config);
}

sub _getValueByTag {
    my $self = shift;
    my %args = @_;

    my $tag        = $args{tag};
    my $parent     = $args{parent};
    my $content    = $args{content};
    my $constraint = lc( $args{constraint} ) || 'or';

    Common::Assert::assert( $tag,                        "tag required" );
    Common::Assert::assert( $constraint =~ /^(and|or)$/, "Constraint must be 'and' or 'or'" );

    my $value;
    my $matchToken;
    my $match;

    delete( $args{tag} );
    delete( $args{parent} );
    delete( $args{constraint} );
    delete( $args{content} );

    Log->debug( sprintf( "Parsing '%s': %s", $tag, defined($content) ? $content : "<undef>" ) );

    my $argCount = scalar keys(%args);

    $self->rewind();

    while ( my $token = $self->search($tag) ) {
        $match = 0;

        foreach my $attrib ( keys(%args) ) {
            if ( $token->attr($attrib) && $token->attr($attrib) =~ /^$args{$attrib}$/ ) {
                $match++;
            }
        }

        #Log->debug( "Tag: $tag, Arg Count: $argCount, Match: $match, Constraint: $constraint" );
        if (   ( $argCount eq 0 )
            || ( $constraint eq 'or'  && $match )
            || ( $constraint eq 'and' && $match eq $argCount ) ) {
            my $tmp = $self->_getText($token);
            Log->debug( sprintf( "Content: %s, Value: %s", $content, $tmp ? $tmp : '<undef>' ) ) if ($content);

            if ( ( $content && $tmp && $tmp =~ /^$content$/ ) || !defined($content) ) {
                $value      = $tmp;
                $matchToken = $token;
                last;
            }
        }
    }

    return ( $value, $matchToken );
}

sub _getText {
    my $self = shift;
    my $node = shift || die;

    my $text = $node->as_text();

    return unless ( defined($text) );

    $text =~ s/^\s+//;
    $text =~ s/\s+$//;

    return $text;
}

sub rewind { shift->{_nodes} = undef }

sub search {
    my $self = shift;
    my $tag  = shift;

    assert($tag);

    unless ( $self->{_nodes} ) {
        my @nodes = $self->parser->look_down( '_tag', $tag );
        $self->{_nodes} = \@nodes;
    }

    my $node = shift @{ $self->{_nodes} } if ( $self->{_nodes} );

    return $node;
}

sub validate {
    my $self = shift;
    my @missingFields;

    # Validate all required fields are populated
    foreach my $field ( $self->_fieldList() ) {
        my $value = ref( $self->$field ) ? $self->$field->access() : $self->$field;
        my $config = $self->_getConfig($field);
        push( @missingFields, $field ) if ( $config->{required} && !defined($value) );
    }

    return "Required fields missing: " . join( ", ", @missingFields ) if (@missingFields);
}

1;
