#!/usr/bin/env perl
#
# Copyright 2008-2010 Tim Rayner
# 
# This file is part of Bio::MAGETAB.
# 
# Bio::MAGETAB is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# 
# Bio::MAGETAB is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with Bio::MAGETAB.  If not, see <http://www.gnu.org/licenses/>.
#
# $Id: 012_builder.t 333 2010-06-02 16:41:31Z tfrayner $

use strict;
use warnings;

use Test::More qw(no_plan);
use Test::Exception;

use Bio::MAGETAB;

BEGIN {
    use_ok( 'Bio::MAGETAB::Util::Builder' );
}

sub confirm_method {

    my ( $builder, $method, $generated_class, $id, $attrs ) = @_;

    my $getter  = "get_$method";
    my $creator = "create_$method";
    my $finder  = "find_or_create_$method";

    dies_ok( sub{ $builder->$getter( $id ) },
             qq{builder getter method doesn't find non-existent $generated_class} );
    
    my $obj;
    lives_ok( sub{ $obj = $builder->$creator( { %$id, %$attrs } ) },
              qq{builder $generated_class create method succeeds} );

    ok( defined $obj, qq{and returns a $generated_class object} );
    ok( $obj->isa( $generated_class ), qq{of the correct $generated_class class} );

    my $obj2;
    lives_ok( sub{ $obj2 = $builder->$finder( { %$id, %$attrs } ) },
              qq{builder $generated_class find_or_create method succeeds} );

    ok( defined $obj2, qq{and returns a $generated_class object} );
    ok( $obj2->isa( $generated_class ), qq{of the correct class} );

    ok( $obj eq $obj2, qq{that is the same $generated_class object generated by the create method} );

    my $obj3 = $builder->$creator( { %$id, %$attrs } );
    ok( $obj ne $obj3,
        qq{builder create method correctly generates an entirely new $generated_class instance} );

    my $obj4;
    lives_ok( sub{ $obj4 = $builder->$getter( $id ) },
              qq{builder getter method finds generated $generated_class} );

    ok( $obj3 eq $obj4,
        qq{and the found object is the latest $generated_class to be instantiated} );

    return;
}

# Dummy values for use in tests.
my $dummy_cv = Bio::MAGETAB::ControlledTerm->new(
    'category' => 'test category',
    'value'    => 'test value',
);
my $dummy_ts = Bio::MAGETAB::TermSource->new(
    'name'  => 'test ts name',
);
my $dummy_node1 = Bio::MAGETAB::DataAcquisition->new(
    'name'  => 'node1',
);
my $dummy_node2 = Bio::MAGETAB::DataAcquisition->new(
    'name'  => 'node2',
);
my $dummy_node3 = Bio::MAGETAB::Normalization->new(
    'name'  => 'node3',
);
my $dummy_factor = Bio::MAGETAB::Factor->new(
    'name'  => 'dummy factor',
);
my $dummy_meas = Bio::MAGETAB::Measurement->new(
    'measurementType'  => 'dummy measurement type',
);
my $dummy_meas2 = Bio::MAGETAB::Measurement->new(
    'measurementType'  => 'dummy measurement type 2',
);
my $dummy_rep = Bio::MAGETAB::Reporter->new(
    'name'  => 'dummy reporter',
);
my $dummy_proto = Bio::MAGETAB::Protocol->new(
    'name'  => 'dummy protocol',
);
my $dummy_proto2 = Bio::MAGETAB::Protocol->new(
    'name'  => 'dummy protocol 2',
);
my $dummy_prapp = Bio::MAGETAB::ProtocolApplication->new(
    'protocol' => $dummy_proto,
);
my $dummy_prapp2 = Bio::MAGETAB::ProtocolApplication->new(
    'protocol' => $dummy_proto2,
);
my $dummy_param = Bio::MAGETAB::ProtocolParameter->new(
    'name'     => 'dummy parameter',
    'protocol' => $dummy_proto,
);
my $dummy_sdrf = Bio::MAGETAB::SDRF->new(
    'uri'   => 'file://not.my.uri/filename.txt'
);
my $dummy_matrix = Bio::MAGETAB::DataMatrix->new(
    'uri'   => 'file://not.my.uri/matrixname.txt',
    'dataType'  => $dummy_cv,
);


# Hash specifying the tests themselves. Keys are as follows:
#
#  class:  The expected class of the returned object.
#  id:     A hashref containing key-value pairs identifying the object.
#  attrs:  A hashref containing other essential attributes for object instantiation.
#  unused: An identifier which hasn't been used in object creation, to test getter failure.
my %test = (
    'array_design'      => { 'class'  => 'Bio::MAGETAB::ArrayDesign',
                             'id'     => { 'name'       => 'test name',
                                           'termSource' => $dummy_ts,
                                           'accession'  => '12345', },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'assay'             => { 'class'  => 'Bio::MAGETAB::Assay',
                             'id'     => { 'name'           => 'test name' },
                             'attrs'  => { 'technologyType' => $dummy_cv },
                             'unused' => { 'name' => 'not a name' },
                         },
    'comment'           => { 'class'  => 'Bio::MAGETAB::Comment',
                             'id'     => { 'name'   => 'test name',
                                           'value'  => 'test value',
                                           'object' => $dummy_node1 },
                             'attrs'  => {},
                             'unused' => [ 'not a name', 'not a value' ],
                         },
    'composite_element' => { 'class'  => 'Bio::MAGETAB::CompositeElement',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'contact'           => { 'class'  => 'Bio::MAGETAB::Contact',
                             'id'     => { 'lastName'    => 'last name',
                                           'firstName'   => 'first name',
                                           'midInitials' => 'mid initials' },
                             'attrs'  => {},
                             'unused' => { 'lastName'    => 'a',
                                           'firstName'   => 'b',
                                           'midInitials' => 'c' },
                         },
    'controlled_term'   => { 'class'  => 'Bio::MAGETAB::ControlledTerm',
                             'id'     => { 'category'   => 'test cat',
                                           'value'      => 'test value',
                                           'termSource' => $dummy_ts,
                                           'accession'  => '12345', },
                             'attrs'  => {},
                             'unused' => { 'category' => 'x',
                                           'value'    => 'y' },
                   },
    'data_acquisition'  => { 'class'  => 'Bio::MAGETAB::DataAcquisition',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'data_file'         => { 'class'  => 'Bio::MAGETAB::DataFile',
                             'id'     => { 'uri'    => 'http://my.test.uri' },
                             'attrs'  => { 'dataType' => $dummy_cv,
                                           'format'   => $dummy_cv },
                             'unused' => { 'uri' => 'http://not.my.uri' },
                         },
    'data_matrix'       => { 'class'  => 'Bio::MAGETAB::DataMatrix',
                             'id'     => { 'uri'  => 'http://my.test.uri' },
                             'attrs'  => { 'dataType' => $dummy_cv },
                             'unused' => { 'uri'      => 'http://not.my.uri' },
                         },
    'database_entry'    => { 'class'  => 'Bio::MAGETAB::DatabaseEntry',
                             'id'     => { 'accession'  => 'test accession',
                                           'termSource' => $dummy_ts },
                             'attrs'  => {},
                             'unused' => { 'accession'  => 'not an accession',
                                           'termSource' => $dummy_ts },
                         },
    'edge'              => { 'class'  => 'Bio::MAGETAB::Edge',
                             'id'     => { 'inputNode'  => $dummy_node1,
                                           'outputNode' => $dummy_node2 },
                             'attrs'  => {},
                             'unused' => { 'inputNode'  => $dummy_node1,
                                           'outputNode' => $dummy_node3 },
                         },
    'extract'           => { 'class'  => 'Bio::MAGETAB::Extract',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'factor'            => { 'class'  => 'Bio::MAGETAB::Factor',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'factor_value'      => { 'class'  => 'Bio::MAGETAB::FactorValue',
                             'id'     => { 'factor' => $dummy_factor,
                                           'term'   => $dummy_cv },
                             'attrs'  => {},
                             'unused' => { 'factor'      => $dummy_factor,
                                           'measurement' => $dummy_meas },
                         },
    'feature'           => { 'class'  => 'Bio::MAGETAB::Feature',
                             'id'     => { 'blockCol'    => 1,
                                           'blockRow'    => 2,
                                           'col'         => 3,
                                           'row'         => 4 },
                             'attrs'  => { 'reporter'    => $dummy_rep },
                             'unused' => { 'blockCol'    => 4,
                                           'blockRow'    => 2,
                                           'col'         => 3,
                                           'row'         => 1 },
                         },
    'investigation'     => { 'class'  => 'Bio::MAGETAB::Investigation',
                             'id'     => { 'title' => 'test title' },
                             'attrs'  => {},
                             'unused' => { 'title' => 'not a title' },
                         },
    'labeled_extract'   => { 'class'  => 'Bio::MAGETAB::LabeledExtract',
                             'id'     => { 'name'  => 'test name' },
                             'attrs'  => { 'label' => $dummy_cv },
                             'unused' => { 'name' => 'not a name' },
                         },
    'matrix_column'     => { 'class'  => 'Bio::MAGETAB::MatrixColumn',
                             'id'     => { 'columnNumber'     => 1,
                                           'data_matrix'      => $dummy_matrix },
                             'attrs'  => { 'quantitationType' => $dummy_cv,
                                           'referencedNodes'  => [ $dummy_node3 ] },
                             'unused' => { 'columnNumber'     => 2,
                                           'data_matrix'      => $dummy_matrix },
                         },
    'matrix_row'        => { 'class'  => 'Bio::MAGETAB::MatrixRow',
                             'id'     => { 'rowNumber'     => 1,
                                           'data_matrix'   => $dummy_matrix },
                             'attrs'  => { 'designElement' => $dummy_rep },
                             'unused' => { 'rowNumber'     => 2,
                                           'data_matrix'   => $dummy_matrix },
                         },
    'measurement'       => { 'class'  => 'Bio::MAGETAB::Measurement',
                             'id'     => { 'measurementType'  => 'test',
                                           'value'            => '10',
                                           'unit'             => $dummy_cv },
                             'attrs'  => {},
                             'unused' => { 'measurementType'  => 'test two',
                                           'value'            => 11,
                                           'unit'             => $dummy_cv },
                         },
    'normalization'     => { 'class'  => 'Bio::MAGETAB::Normalization',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'parameter_value'   => { 'class'  => 'Bio::MAGETAB::ParameterValue',
                             'id'     => { 'parameter'   => $dummy_param,
                                           'protocol_application' => $dummy_prapp },
                             'attrs'  => {},
                             'unused' => { 'parameter'   => $dummy_param,
                                           'protocol_application' => $dummy_prapp2, },
                         },
    'protocol'          => { 'class'  => 'Bio::MAGETAB::Protocol',
                             'id'     => { 'name'       => 'test name',
                                           'termSource' => $dummy_ts,
                                           'accession'  => '12345', },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'protocol_application' => { 'class'  => 'Bio::MAGETAB::ProtocolApplication',
                                'id'     => { 'protocol' => $dummy_proto },
                                'attrs'  => {},
                                'unused' => { 'protocol' => $dummy_proto2 },
                            },
    'protocol_parameter'   => { 'class'  => 'Bio::MAGETAB::ProtocolParameter',
                                'id'     => { 'name'     => 'test name',
                                              'protocol' => $dummy_proto },
                                'attrs'  => {},
                                'unused' => { 'name'     => 'test name 2',
                                              'protocol' => $dummy_proto2 },
                   },
    'publication'       => { 'class'  => 'Bio::MAGETAB::Publication',
                             'id'     => { 'title' => 'test title' },
                             'attrs'  => {},
                             'unused' => { 'title' => 'not a title' },
                         },
    'reporter'          => { 'class'  => 'Bio::MAGETAB::Reporter',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'sdrf'              => { 'class'  => 'Bio::MAGETAB::SDRF',
                             'id'     => { 'uri' => 'http://my.sdrf.uri' },
                             'attrs'  => {},
                             'unused' => { 'uri' => 'http://not.my.uri' },
                         },
    'sdrf_row'          => { 'class'  => 'Bio::MAGETAB::SDRFRow',
                             'id'     => { 'rowNumber' => 1,
                                           'sdrf'      => $dummy_sdrf },
                             'attrs'  => { 'nodes'     => [ $dummy_node3 ] },
                             'unused' => { 'rowNumber' => 2,
                                           'sdrf'      => $dummy_sdrf },
                         },
    'sample'            => { 'class'  => 'Bio::MAGETAB::Sample',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'source'            => { 'class'  => 'Bio::MAGETAB::Source',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
    'term_source'       => { 'class'  => 'Bio::MAGETAB::TermSource',
                             'id'     => { 'name' => 'test name' },
                             'attrs'  => {},
                             'unused' => { 'name' => 'not a name' },
                         },
);

# FIXME we may want to test more instantiation options.
my $builder;
lives_ok( sub{ $builder = Bio::MAGETAB::Util::Builder->new({
    namespace => 'test_namespace',
    authority => 'test_authority',
}) }, q{Object constructor succeeds} );
ok( defined $builder, q{and returns an object} );
ok( $builder->isa('Bio::MAGETAB::Util::Builder'), q{of the correct class} );

while ( my ( $method, $data ) = each %test ) {

    confirm_method( $builder,
                    $method,
                    $data->{'class'},
                    $data->{'id'},
                    $data->{'attrs'} );

    my $getter = "get_$method";
    dies_ok( sub{ $builder->$getter( $data->{'unused'} ) },
             qq{builder still fails to find non-existent $data->{class} object} );
}

# TODO: test updating an object in find_or_create_*
