#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Common::FormHash;

use strict;
use warnings;
use Carp;

use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::FormObject;
use base 'Common::FormObject';

# So, to make this abstract, I need to abstract:
# - The name of the 'container' tag.  In this case, 'Term'.

sub _tagName {

    # Override this method to return the name of the tag.
    # For example:
    # return "Thingy";
    #
    # This would mean that the XML would be a list of
    # <Thingy k="foo">...</Thingy>
    # <Thingy k="bar">...</Thingy>

    assert( 0, "You _must_ override _tagName()" );
}

sub _getItemCollection {
    my ( $self, %args ) = @_;

    # Override this to return the ItemCollection, based on the args.
    #

    assert( 0, "You _must_ override _getItemCollection()" );
}

sub _keyMethodName {

    # Override this to return the name of the accessor method that contains
    # the primary key.
    # For example, if we have a class that stores Album objects, this method
    # would return 'AlbumID';
    #
    assert( 0, "You _must_ override _keyMethodName()" );
}

sub _getNewObject {

    # Override this to return a new object to store in the container.
    # This is usually called when dynamically adding objects during
    # a assignCGIParams call.
    #
    assert( 0, "You _must_ override _getNewObject()" );
}

sub _getObjectFromDBItem {
    my ( $self, $dbItem ) = @_;
    assert( 0, "You _must_ override _getObjectFromDBItem()" );
}

sub _addObject {
    my ( $self, $object ) = @_;

    # This method adds a FormObject (or XMLObject) to the container.
    # The base implementation (this method) simply inserts it.
    # You can override this method if you want to do additional stuff when
    # iterating over the ItemCollection... Just remember to call the SUPER method.
    #

    # First, get the key.
    #
    my $method = $self->_keyMethodName();
    my $key    = $object->$method();

    # Now insert it.
    #
    my $tag = $self->_tagName();
    $self->{$tag}{$key} = $object;
}

sub _init {
    my ( $self, %args ) = @_;

    $self->SUPER::_init(%args);

    # Always create a hash, even if it will be empty.
    #
    my $tag = $self->_tagName();
    $self->{$tag} = {};

    my $collection = $self->_getItemCollection(%args);
    if ($collection) {
        while ( my $item = $collection->next() ) {
            my $obj = $self->_getObjectFromDBItem($item);
            $self->_addObject($obj);
        }
    }

    return $self;
}

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

    my $hash = $self->getHash();
    return scalar keys %$hash;
}

# If you need the tag name for something besides XML
#
sub getTagName {
    my ($self) = @_;

    return $self->_tagName();
}

# If you want your contained objects to output in a sorted
# order, override this method to do the sorting.
#
sub getKeys {
    my ($self) = @_;

    my $tag  = $self->_tagName();
    my @keys = keys %{ $self->{$tag} };
    return \@keys;
}

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

    my $tag = $self->_tagName();
    return $self->{$tag};
}

# I believe we can make 'validate' and 'save' abstract.
#
sub validate {
    my $self = shift;
    my %args = @_;

    my $res;

    my $valid = 1;
    my $hash  = $self->getHash();
    my $keys  = $self->getKeys();
    foreach my $key (@$keys) {
        my $obj = $hash->{$key};
        next if ( !defined $obj );

        $res = $obj->validate(%args);
        if ( !$res ) {
            $valid = 0;
        }
    }

    return $valid;
}

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

    my $hash = $self->getHash();
    foreach my $key ( $self->getKeys() ) {
        my $obj = $hash->{$key};
        next if ( !defined $obj );

        $obj->save();
    }
}

# !!! The 'old' way was to override the 'tag' accessor.
# !!! This is going to be hard to do abstractly - we already have an AUTOLOAD
# !!! method in this class hierarchy, I don't want to try and layer on another.
#
# !!! However, I think we _can_ override _accessProperty and accomplish what we
# !!! need to do - and, hopefully, get rid of the whole '_listProperty' thing
#

sub _accessProperty {
    my ( $self, $addressList, $newValue ) = @_;
    my $localProperty = shift @$addressList;

    confess "unknown method \"$localProperty\" invoked on object " . Dumper($self) unless exists $self->{$localProperty};

    # Assume that the next thing on the addressList is the key
    #
    my $key  = shift @$addressList;
    my $hash = $self->getHash();

    my $tag = $self->_tagName();

    # If there's nothing left in the addressList, then we're trying to access/assign the
    # whole object stored in the container.
    #
    if ( 0 == scalar @$addressList ) {
        if ( defined $newValue ) {
            $hash->{$key} = $newValue;
        }
        return $hash->{$key};
    }

    # If we're at this point, then we're trying to access a 'deep' property of an object
    # stored in this container.
    #
    # If we don't actually have an object stored with that key, then we will go ahead
    # and create one.
    #
    if ( !defined $hash->{$key} ) {
        my $newObj = $self->_getNewObject();
        $hash->{$key} = $newObj;
    }

    return $hash->{$key}->_accessProperty( $addressList, $newValue );
}

sub writeXML {
    my ( $self, $xw, $tag, %extraParams ) = @_;

    return if $self->skipXML();

    if ($tag) {

        # If we have some extra params, we'll want to add those to our outgoing XMLParams;
        #
        my %outParams = $self->getXMLParams();
        foreach my $key ( keys %extraParams ) {
            $outParams{$key} = $extraParams{$key};
        }
        $xw->starttag( $tag, %outParams );
    }

    # We'll need to know the tag name of the container.
    #
    my $containerTag = $self->_tagName();

    foreach my $key ( sort keys %{$self} ) {
        next if ( '_' eq substr( $key, 0, 1 ) );

        if ( $key eq $containerTag ) {
            my $hash = $self->getHash();
            my $keys = $self->getKeys();
            foreach my $hashKey ( @{$keys} ) {
                Common::WriteXML::writeXML( $xw, $hash->{$hashKey}, $key, k => $hashKey );
            }
        } else {
            Common::WriteXML::writeXML( $xw, $self->{$key}, $key );
        }
    }

    if ($tag) {
        $xw->endtag($tag);
    }
}

###
1;    #
###
