# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2006
# Novell. All Rights Reserved.
#
# Contributor(s): David Koenig   <dkoenig@novell.com>
#                 Greg Hendricks <ghendricks@novell.com>
#                 Jeff Dayley    <jedayley@novell.com>

package Bugzilla::Extension::Testopia::Importer;

use strict;

use Bugzilla::Error;
use Bugzilla::Extension::Testopia::Attachment;
use Bugzilla::Extension::Testopia::TestPlan;
use Bugzilla::Extension::Testopia::TestCase;
use Bugzilla::Extension::Testopia::Category;

use Class::Struct;
use Data::Dumper;
use MIME::Base64;
use XML::Validator::Schema;

local our @attachments;
local our @caseruns;
local our @cases;
local our @plans;
local our @runs;
local our $xref = { 'cases' => {}, 'plans' => {}, 'runs' => {} };
local our $debug = 0;

struct( 'Bugzilla::Extension::Testopia::Importer', {debug => '$'} );

sub _check_category {
    my ( $twig, $e ) = @_;
    print "Checking categories...\n" if $debug;
    my $product = Bugzilla::Product->check($e->field('tr:product'));
    if ( !Bugzilla::Extension::Testopia::Category::check_case_category( $e->field('tr:name'), $product ) ) {
        Bugzilla::Extension::Testopia::Category->create(
            {
                product_id => $product->id,
                name       => $e->field('tr:name'),
            }
        );
    }
    return 1;
}

sub _check_build {
    my ( $twig, $e ) = @_;
    print "Checking builds...\n" if $debug;
    my $product = Bugzilla::Product->check($e->field('tr:product'));
    if ( !Bugzilla::Extension::Testopia::Build::check_build( $e->field('tr:name'), $product ) ) {
        Bugzilla::Extension::Testopia::Build->create(
            {
                product_id => $product->id,
                name       => $e->field('tr:name'),
            }
        );
    }
    return 1;
}

sub _check_environment {
    my ( $twig, $e ) = @_;
    print "Checking environments...\n" if $debug;
    my $product = Bugzilla::Product->check($e->field('tr:product'));
    if ( !Bugzilla::Extension::Testopia::Environment::check_environment( $e->field('tr:name'), $product ) ) {
        Bugzilla::Extension::Testopia::Environment->create(
            {
                product_id => $product->id,
                name       => $e->field('tr:name'),
            }
        );
    }
    return 1;
}

sub _check_milestone {
    my ( $twig, $e ) = @_;
    print "Checking milestones...\n" if $debug;
    my $product = Bugzilla::Product->check($e->parent->field('tr:product'));
    Bugzilla::Milestone->check( { product => $product, name => $e->parent->field('tr:milestone') } );
    return 1;
}

sub process_caserun {
    my ( $twig, $e ) = @_;
    my $params;
    $params->{iscurrent}          = $e->field('tr:iscurrent') eq 'true' ? 1 : 0;
    $params->{case_id}            = $e->first_child('tr:case')->{'att'}->{'id'};
    $params->{run_id}             = $e->first_child('tr:run')->{'att'}->{'id'};
    $params->{build_id}           = $e->first_child('tr:build')->field('tr:name');
    $params->{environment_id}     = $e->first_child('tr:environment')->field('tr:name');
    $params->{case_run_status_id} = $e->field('tr:status');
    $params->{assignee}           = $e->first_child('tr:assignee')->field('tr:login') if $e->first_child('tr:assignee');
    $params->{testedby}           = $e->first_child('tr:testedby')->field('tr:login') if $e->first_child('tr:testedby');
    $params->{case_text_version}  = $e->field('tr:case_text_version');
    $params->{priority_id}        = $e->field('tr:priority');
    $params->{notes}              = $e->field('tr:notes');
    $params->{sortkey}            = $e->field('tr:sortkey');
    $params->{running_date}       = $e->field('tr:running_date') || undef;
    $params->{close_date}         = $e->field('tr:close_date') || undef;
    
    Bugzilla::Extension::Testopia::TestCaseRun->run_import_validators($params);

    push @caseruns, $params;
    return 1;

}

sub process_attachment {
    my ( $twig, $e ) = @_;
    my $params;
    $params->{submitter_id} = $e->first_child('tr:submitter')->field('tr:login') || Bugzilla->user->id;
    $params->{description}  = $e->field('tr:description');
    $params->{filename}     = $e->field('tr:filename');
    $params->{mime_type}    = $e->field('tr:mime_type');
    if ( defined $e->first_child('tr:contents') ) {
        my $encoding = $e->first_child('tr:contents')->{'att'}->{'encoding'};
        if ( $encoding && $encoding =~ /base64/ ) {
            $params->{contents} = decode_base64( $e->field('tr:contents') );
        }
        else {
            $params->{contents} = $e->field('tr:contents');
        }
    }
    Bugzilla::Extension::Testopia::Attachment->run_create_validators($params);
    push @attachments, $params;
    return 1;
}

sub process_case {
    my ( $twig, $e ) = @_;

    my $params = {};
    my @plans;
    my @comps;
    my @att;
    my @tags = $e->children_text('tr:tag');
    foreach my $comp ( $e->children('tr:component') ) {
        push @comps,
          {
            product   => $comp->field('tr:product'),
            component => $comp->field('tr:name'),
          };
    }
    push @plans, split( ',', $e->field('tr:linked_plans') );
    push @att, $_ foreach (@attachments);

    @attachments = ();

    $params->{'alias'}             = $e->field('tr:alias');
    $params->{'case_status_id'}    = $e->field('tr:case_status');
    $params->{'category_id'}       = $e->first_child('tr:category')->field('tr:name');
    $params->{'priority_id'}       = $e->field('tr:priority');
    $params->{'isautomated'}       = $e->field('tr:isautomated') eq 'true' ? 1 : 0;
    $params->{'estimated_time'}    = $e->field('tr:estimated_time');
    $params->{'script'}            = $e->field('tr:script');
    $params->{'arguments'}         = $e->field('tr:arguments');
    $params->{'summary'}           = $e->field('tr:summary');
    $params->{'requirement'}       = $e->field('tr:requirement');
    $params->{'author_id'}         = $e->first_child('tr:author')->field('tr:login') || Bugzilla->user->id;
    $params->{'action'}            = $e->first_child('tr:text')->field('tr:action');
    $params->{'effect'}            = $e->first_child('tr:text')->field('tr:expected_result');
    $params->{'setup'}             = $e->first_child('tr:text')->field('tr:setup');
    $params->{'breakdown'}         = $e->first_child('tr:text')->field('tr:breakdown');
    $params->{'dependson'}         = $e->field('tr:dependson');
    $params->{'blocks'}            = $e->field('tr:blocks');
    $params->{'tags'}              = \@tags;
    $params->{'bugs'}              = $e->field('tr:bugs');
    $params->{'plans'}             = \@plans;
    $params->{'components'}        = \@comps;
    $params->{'default_tester_id'} = $e->first_child('tr:default_tester')->field('tr:login') if $e->first_child('tr:default_tester');

    $params->{'attachments'} = \@att;
    $params->{'xid'}         = $e->{'att'}->{'id'};

    $xref->{cases}->{ $e->{'att'}->{'id'} } = undef;

    Bugzilla::Extension::Testopia::TestCase->run_import_validators($params);

    push @cases, $params;
    return 1;
}

sub process_run {
    my ( $twig, $e ) = @_;
    my $params = {};
    my @crs;

    push @crs, $_ foreach (@caseruns);

    @caseruns = ();

    $params->{'summary'}           = $e->field('tr:summary');
    $params->{'plan_id'}           = $e->first_child('tr:plan')->{'att'}->{'id'};
    $params->{'plan_text_version'} = $e->field('tr:plan_text_version');
    $params->{'product_version'}   = $e->field('tr:product_version');
    $params->{'build_id'}          = $e->first_child('tr:build')->field('tr:name');
    $params->{'environment_id'}    = $e->first_child('tr:environment')->field('tr:name');
    $params->{'manager_id'}        = $e->first_child('tr:manager')->field('tr:login') || Bugzilla->user->id;
    $params->{'start_date'}        = $e->field('tr:start_date') || undef;
    $params->{'stop_date'}         = $e->field('tr:stop_date') || undef;
    $params->{'plan_text_version'} = $e->field('tr:plan_text_version');
    $params->{'notes'}             = $e->field('tr:notes');
    $params->{'target_pass'}       = $e->field('tr:target_pass_rate');
    $params->{'target_completion'} = $e->field('tr:target_completion_rate');

    $params->{'caseruns'}          = \@crs;
    $params->{'xid'}               = $e->{'att'}->{'id'};
    
    $xref->{runs}->{ $e->{'att'}->{'id'} } = undef;
    
    Bugzilla::Extension::Testopia::TestRun->run_import_validators($params);
    
    push @runs, $params;
    return 1;
}

sub process_plan {
    my ( $twig, $e ) = @_;
    my $params = {};
    my @att;
    my @tags = $e->children_text('tr:tag');
    push @att, $_ foreach (@attachments);
    @attachments = ();

    $params->{'product_id'}              = $e->field('tr:product');
    $params->{'author_id'}               = $e->first_child('tr:author')->field('tr:login') || Bugzilla->user->id;
    $params->{'type_id'}                 = $e->field('tr:plan_type');
    $params->{'name'}                    = $e->field('tr:name');
    $params->{'text'}                    = $e->first_child('tr:document')->field('tr:text');
    $params->{'tags'}                    = \@tags;
    $params->{'default_product_version'} = $e->field('tr:default_product_version');

    $params->{'attachments'} = \@att;
    $params->{'xid'}         = $e->{'att'}->{'id'};

    $xref->{plans}->{ $e->{'att'}->{'id'} } = undef;

    Bugzilla::Extension::Testopia::TestPlan->run_create_validators($params);

    push @plans, $params;
    return 1;
}

sub parse {
    my ( $self, $xml, $product, $plans ) = @_;
    $debug = $self->debug;
    my $validator = XML::Validator::Schema->new( file => 'extensions/Testopia/testopia.xsd' );
    my $parser = XML::SAX::ParserFactory->parser( Handler => $validator );

    my $twig = XML::Twig->new(
        keep_encoding => 1,
        twig_handlers => {
            'tr:category'    => \&_check_category,
            'tr:build'       => \&_check_build,
            'tr:milestone'   => \&_check_milestone,
            'tr:environment' => \&_check_environment,
            'tr:testcase'    => \&process_case,
            'tr:testplan'    => \&process_plan,
            'tr:testcaserun' => \&process_caserun,
            'tr:testrun'     => \&process_run,
            'tr:attachment'  => \&process_attachment,
        },
    );

    # Check if we have a file
    if ( -e $xml ) {
        eval{
            $parser->parse_file($xml);
            $twig->parsefile($xml);            
        };
        if ($@){
            print STDERR $@;
            ThrowUserError('import_parse_error', {type => 'XML', msg => $@})
        }
    }
    elsif ( $xml =~ /^<\?xml/ ) {
        eval{
            $parser->parse_string($xml);
            $twig->parse($xml);            
        };
        if ($@){
            print STDERR $@;
            ThrowUserError('import_parse_error', {type => 'XML', msg => $@})
        }
    }
    else {
        ThrowUserError('invlid-import-source');
    }

    ###
    # Start by importing any Test Plans
    # We will assume that if a product was passed to parse that we should use
    # that product instead of whatever was included in the plan.
    ###
    foreach my $params (@plans) {
        $params->{product_id} = $product if $product;

        my $attachments = $params->{attachments};
        my $xid         = $params->{xid};

        delete $params->{xid};
        delete $params->{attachments};

        my $plan = Bugzilla::Extension::Testopia::TestPlan->create($params);
        foreach my $a (@$attachments) {
            $a->{'plan_id'} = $plan->id;
            Bugzilla::Extension::Testopia::Attachment->create($a);
        }

        $xref->{plans}->{$xid} = $plan->id;
        $params->{xid} = $plan->id;
    }

    my @caseplans;
    if ($plans) {
        push @caseplans, new Bugzilla::Extension::Testopia::TestPlan($_) foreach split( ',', $plans );
    }

    foreach my $params (@cases) {
        if ($plans) {
            $params->{plans} = \@caseplans;
        }
        else {
            my @linkedplans;
            foreach my $pid ( @{ $params->{plans} } ) {
                if ( $xref->{plans}->{$pid} ) {
                    push @linkedplans, new Bugzilla::Extension::Testopia::TestPlan( $xref->{plans}->{$pid} );
                }
            }
            ThrowUserError('plan_needed') unless scalar @linkedplans;
            $params->{plans} = \@linkedplans;
        }

        my $attachments = $params->{attachments};
        my $xid         = $params->{xid};

        delete $params->{xid};
        delete $params->{attachments};
        
        my $case = Bugzilla::Extension::Testopia::TestCase->create($params);
        foreach my $a (@$attachments) {
            $a->{'case_id'} = $case->id;
            Bugzilla::Extension::Testopia::Attachment->create($a);
        }

        $xref->{cases}->{$xid} = $case->id;
        $params->{xid} = $case->id;

    }
    foreach my $params (@cases) {
        my @blocks;
        my @dependson;
        foreach my $tb ( split( /[\s,]+/, $params->{blocks} ) ) {
            push @blocks, $xref->{cases}->{$tb} if $xref->{cases}->{$tb};
        }
        foreach my $tb ( split( /[\s,]+/, $params->{dependson} ) ) {
            push @dependson, $xref->{cases}->{$tb} if $xref->{cases}->{$tb};
        }
        my $case = Bugzilla::Extension::Testopia::TestCase->new( $params->{xid} );
        $case->set_blocks( join( ',', @blocks ) );
        $case->set_dependson( join( ',', @dependson ) );
        $case->update();

    }
    
    foreach my $params (@runs) {
        my $caseruns = $params->{caseruns};
        my $xid      = $params->{xid};
        
        $params->{'start_date'} =~ s/T/ / if $params->{'start_date'};
        $params->{'stop_date'}  =~ s/T/ / if $params->{'stop_date'};
        $params->{'status'}     = $params->{'stop_date'} ? 0 : 1;

        delete $params->{xid};
        delete $params->{caseruns};
        
        $params->{plan_id} = $xref->{plans}->{$params->{plan_id}};

        my $run = Bugzilla::Extension::Testopia::TestRun->create($params);
        foreach my $c (@$caseruns) {
            
            if ($xref->{cases}->{$c->{case_id}}){
                $c->{running_date} =~ s/T/ / if $c->{running_date};
                $c->{close_date} =~ s/T/ / if $c->{close_date};
                $c->{case_id} = $xref->{cases}->{$c->{case_id}};
                $c->{run_id} = $run->id;
                
                Bugzilla::Extension::Testopia::TestCaseRun->create($c);
            }
        }

        $xref->{runs}->{$xid} = $run->id;
        $params->{xid} = $run->id;
    }

    print Data::Dumper::Dumper($xref) if $debug;
    my @newcases = values %{$xref->{cases}}; 
    return \@newcases;

}

1;

__END__

=head1 NAME

Bugzilla::Extension::Testopia::Importer

=head1 DESCRIPTION

Importer is responsible for importing test plans, cases, and runs from XML.  

=head1 SEE ALSO

See tr_importxml.pl for usage.
See testopia.xsd for format

