#!/usr/local/bin/perl5.6.1

package gt_proto_api;


use strict;
use sql_engine;
use Carp;
use Data::Dumper;
#require "/nfs/team71/psg/dn1/badger-bin/BarcodeRevised_V2_0.pm";
#use Checkbarcode;
use Time::HiRes qw(gettimeofday);
#use Mail::Mailer;


#################
# See POD at end
#################
#warn "Using local version of gt_proto_api.pm !!!\n";


###############################
# Externally accessed routines
###############################

sub return_prefix {
    my ($self, $pe_id) = @_;
    return $self->{entities}->{$pe_id}->{prefix};
    
}


sub return_internal_id {
    my ($self, $pe_id) = @_;
    return $self->{entities}->{$pe_id}->{internal_id};
    
}



sub rollback_changes {
    my ($self) = @_;
    #warn "----------------------------------\n";
    #warn "Rolling back\n";
    #warn "----------------------------------\n";
    $self->{gtr_database}->{DBH}->rollback;
}


sub commit_changes {
    my ($self) = @_;
    #warn "----------------------------------\n";
    #warn "commiting\n";
    #warn "----------------------------------\n";
    $self->{gtr_database}->{DBH}->commit;
}


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

    warn Data::Dumper::Dumper($self);

}


######################################################################################
# A container for physical_entities; ie. from here we can make them and retrieve them
# A container may contain multiple entities ?
######################################################################################
sub new {
    my ($class, $gtr_db, $id_role, $benchmark) = @_;

    croak "gt_proto_api.pm new method ERROR No GTR WrapDBI user id supplied !" unless (defined $gtr_db);
    croak "gt_proto_api.pm new method ERROR No GTR WrapDBI id_role supplied !" unless (defined $id_role);
    croak "gt_proto_api.pm new method ERROR No benchmark level supplied !" unless (defined $benchmark);


    unless ($benchmark eq 'NB' || $benchmark eq 'BM' || $benchmark eq 'BMS'){
	croak "gt_proto_api.pm new method ERROR Benchmark level must be one of NB, BM or BMS";
    }
    
    if ($benchmark eq 'NB'){
	$benchmark = 0;
    } elsif ($benchmark eq 'BM') {
	$benchmark = 1;
	print STDOUT "Benchmarking activated.\n";
    } else {
	$benchmark = 2;
    }


    # Create the object
    my $self = {};
    bless $self, $class;




    
    ###################################################
    # Register valid subroutines for entity population
    ###################################################
    my $valid_population_subs = { 
	'3' => {'new' => \&pop_entity_new, 
		#'pre_existing' => \&pop_3_pre_populate},
	    },
    };
    $self->{valid_population_subs} = $valid_population_subs;



    #############################
    # Handle database connection
    #############################
    #############################################################
    # Limit dev release to the appropriate development databases
    #############################################################
    if ($gtr_db ne 'gt_proto_api'){
    	croak "This is a test version of gt_proto_api.pm WrapDBI user should be gt_proto_api !\n";
    }


    if ($benchmark == 1){ 
	$self->start_timer("connection to DB and preparing gt_proto_1.sql_lib::all");
    };
    $self->{gtr_database} = statements->new("$gtr_db") || die;
    $self->{gtr_database}->default_sql_lib_path('/nfs/team71/psg/dn1/badger-bin/platetrack/');
    $self->{gtr_database}->load_sql_lib(["gt_proto_1.sql_lib::all"]);
    if ($benchmark == 1){ 
	$self->stop_timer;
    };
    

    #################################################
    # Limit certain roletypes to certain actions ???
    #################################################
    # TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO 
    $self->{id_role} = $id_role;

    ##############################################
    # Derive a teamname from the supplied id_role
    ##############################################
    my $teamname = $self->{gtr_database}->get('so_proto_1.sql_lib::get_team_from_id_role', [$id_role]);
    $teamname = $teamname->[0]->[0];
    croak "gt_proto_api.pm new method ERROR The supplied id_role $id_role does not produce a valid teamname!" unless (defined $teamname);
    $self->{teamname} = $teamname;

    return $self;
}



#######################################################
# Register a new entity into the container
# eg. make a new GT loadingplate
#
# Supply type of entity we are talking about
# Supply creation method required
#   - make a new one from scratch
#   - populate from pre-existing entry in the database
#######################################################
sub physical_entity {
    my ($self, $entity_type, $creation_type) = @_;

    ###############################################
    # Store information about this physical entity
    ###############################################
    my $this_entity = {};

    ######################
    # Required parameters
    ######################
    croak "gt_proto_api.pm physical_entity method ERROR No entity_type parameter supplied !" unless (defined $entity_type);
    croak "gt_proto_api.pm physical_entity method ERROR No creation_type parameter supplied !" unless (defined $creation_type);

    ################
    # creation_type
    ################
    # new;                 create a new entity of this type
    # pre_existing;        create a pre-existing entity of this type
    my $allowable_creation = 
    { 
	"new" => 1, 
	#"pre_existing" => 1
	};

    #warn Data::Dumper::Dumper($allowable_creation);
    unless ( defined ($allowable_creation->{$creation_type}) ){
	croak "gt_proto_api.pm physical_entity method ERROR creation_type parameter $creation_type invalid !";
    }
	     
    ################
    # entity_type
    ################
    # TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO 
    # Different entity types will use different sequences; we should look this up

    my $entity_types =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_entity_types_by_id');
    my $allowable_entities;
    foreach my $m (@$entity_types){
	$allowable_entities->{$m->[0]} = $m->[1];
    }
    
    unless ( defined ($allowable_entities->{$entity_type}) ){
	croak "gt_proto_api.pm physical_entity method ERROR entity_type parameter $entity_type invalid !";
    }

    $self->{allowable_entity_types_by_id} = $allowable_entities;

    $this_entity->{entity_type} = $entity_type;
    $this_entity->{creation_type} = $creation_type;

    #######################################################
    # We need a PK identifier for this new physical entity
    #######################################################
    my $this_entity_id =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_entity_id');
    $this_entity_id = $this_entity_id->[0]->[0];
    $this_entity->{identifier} = $this_entity_id;
    

    ######################################
    # Load our new entity into our object
    ######################################
    $self->{entities}->{$this_entity_id} = $this_entity;
 
    return ($this_entity_id);

}




#################################################################################################################
# How we populate our structure depends on the pre-defined physical entity type and the predefined creation type
#################################################################################################################
sub populate_entity {
    my ($self, $pe_id) = @_;
    
    ####################################################################
    # To populate a Physical_Entity, an ID for the must be supplied
    ####################################################################
    croak "gt_proto_api.pm populate_entity method ERROR No Physical_Entity Id supplied !" unless (defined $pe_id);

    ########################################################
    # Is the provided entity_id registered in the container
    ########################################################
    unless (defined $self->{entities}->{$pe_id}){
	croak "gt_proto_api.pm populate method Entity_Id $pe_id supplied is not known !\n";
    }


    #################################################################################
    # If the creation type is 'new';
    # we may be supplied with an internal_id? or an external_id (eg. EPIG externally
    # generated barcode)
    #################################################################################
    my $creation_type = $self->{entities}->{$pe_id}->{creation_type};

    #############################################################################
    # Entity_type info from the object in question
    #############################################################################
    my $entity_type = $self->{entities}->{$pe_id}->{entity_type};
#    my $seqobject_type_id = $self->{allowable_seqobjects}->{$self->{seqobjects}->{$so_id}->{seqobject_type}};

    



    ######################################################################
    # Can we populate the given creation_type and entity_type combination
    # Work out which subroutine to call
    ######################################################################
    unless (defined ($self->{valid_population_subs}->{$entity_type}->{$creation_type})){
	
	if ($creation_type eq 'new'){
	    &pop_entity_new($self, $pe_id, $entity_type);
	} else {
	    croak "gt_proto_api.pm populate method ERROR no valid populate method exists for entity_type $entity_type and creation_type $creation_type !";
	}
	
    } else {
	my $subroutine = "pop_" . "$entity_type" . "_" . "$creation_type";
	#warn "Calling subroutine $subroutine\n";
	&{$self->{'valid_population_subs'}->{$entity_type}->{$creation_type}}   ($self, $pe_id, $entity_type);
    }
   
}


sub populate_entity_contents {

    my ($self, $pe_id, $id_map, $contents_info) = @_;

    croak "gt_proto_api.pm populate_entity_contents method ERROR No Physical entity ID supplied !" unless (defined $pe_id);
    
    croak "gt_proto_api.pm populate_entity_contents method ERROR No map ID supplied !" unless (defined $id_map);
    # TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO 
    # Check sensible values ?

    croak "gt_proto_api.pm populate_entity_contents method ERROR No contents info supplied !" unless (defined $contents_info);
    # TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
    # Do we ALWAYS have to provide this info ?
    

    ########################################################
    # Does the provided entity exist within the container ?
    ########################################################
    unless (defined $self->{entities}->{$pe_id}){
	croak "gt_proto_api.pm populate method Entity_Id $pe_id supplied is not known !\n";
    }
    
    #######################################################
    # We need a PK identifier for this new entity_content
    #######################################################
    my $this_entity_contents_id =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_entity_contents_id');
    $this_entity_contents_id = $this_entity_contents_id->[0]->[0];
    $self->{entities}->{$pe_id}->{well_info}->{$id_map}->{id_entity_content} = $this_entity_contents_id;

    # Record the meta info with the correct well
    $self->{entities}->{$pe_id}->{well_info}->{$id_map}->{contents_info} = $contents_info;

}





sub update {
     my ($self, $pe_id) = @_;

     #####################################################################
     # To create a Physical_Entity, an ID for the Entity must be supplied
     #####################################################################
     croak "gt_proto_api.pm update method ERROR No Entity Id supplied !" unless (defined $pe_id);
     
     #################################################################
     # Does the supplied Entity actually exist in our collection ?
     #################################################################
     unless (defined $self->{entities}->{$pe_id}){
	 croak "gt_proto_api.pm update method ERROR Entity Id $pe_id supplied is not known !";
     }
     
     #################################################
     # Has the Entity in question been populated ?
     #################################################
     unless (defined ($self->{entities}->{$pe_id}->{populated})){
	 croak "gt_proto_api.pm update method ERROR Entity $pe_id has not been populated!";
     }
     
     
     #################################################################
     # creation_type new -> insert    pre_existing -> possible update
     #################################################################
     my $creation_type = $self->{entities}->{$pe_id}->{creation_type};
     if ($creation_type eq 'new'){
	 $self->create_new_entity($pe_id);
     } else {
	 die "Unhandled creation type !\n";
     }

}





# ------------------------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------------------



###############################
# Internally accessed routines
###############################

######################
# Population routines
######################
sub pop_entity_new {

    my ($self, $pe_id, $entity_type) = @_;
    
    my ($pack, $file, $line) = caller;
    unless ($pack eq __PACKAGE__ && $file eq __FILE__){
        croak "gt_proto_api.pm pop_entity_new method ERROR Invalid access to subroutine from $pack";
    }
    
    $self->{entities}->{$pe_id}->{populated} = 1;


    
    ######################################################################################
    # If this entity is registered against a barcode prefix then generate an  INTERNAL_ID
    # from the registered sequence
    ######################################################################################
    my $prefix_sequence =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_prefix_sequence', [$entity_type]);
    my $prefix = $prefix_sequence->[0]->[0];
    my $sequence = $prefix_sequence->[0]->[1];
    
    
    my $internal_id;
    if (defined $sequence){
	
	####################################################
	# Get the next number from the sequence in question
	####################################################
	if ($sequence eq 'SEQ_GTMPLATE'){
	    $internal_id =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_SEQ_GTMPLATE');
	    $internal_id = $internal_id->[0]->[0];

	} elsif ($sequence eq 'SEQ_GTFPLATE'){
	    $internal_id =  $self->{gtr_database}->get('so_proto_1.sql_lib::get_SEQ_GTFPLATE');
	    $internal_id = $internal_id->[0]->[0];

	} else {

	    ##############################
	    # A sequence we cannot handle
	    ##############################
	    croak "gt_proto_api.pm populate_entity method ERROR sequence $sequence cannot be handled !";
	    
	}
    }

    $self->{entities}->{$pe_id}->{internal_id} = $internal_id;
    $self->{entities}->{$pe_id}->{prefix} = $prefix;
    $self->{entities}->{$pe_id}->{sequence} = $sequence;

}



sub create_new_entity {
    
    my ($self, $pe_id) = @_;

    my ($pack, $file, $line) = caller;
    unless ($pack eq __PACKAGE__ && $file eq __FILE__){
        croak "gt_proto_api.pm create_new_entity method ERROR Invalid access to subroutine from $pack";
    }
    
    #################################################################################
    # Physical_Entities  will always need entries for PHYSICAL_ENTITY, ENTITY_STATUS
    # A PHYSICAL_ENTITY will have varying ENTITY_CONTENTS entries depending on the
    # format.
    # The requirements CONTENTS_INFO will depend on the entity-type but
    # the database inserts will be the same
    ######################################################################

    #########################
    # Make a PHYSICAL_ENTITY
    #########################
    #warn "---------------------------------------------------------------------------------------------\n";
    #warn "Calling make_physical_entity with ...\n";
    #warn "ID_ENTITY ... $pe_id\n";
    #warn "ID_ENTITY_TYPE ... $self->{entities}->{$pe_id}->{entity_type}\n";
    # cf. so_proto_api
    #warn "ID_ENTITY_TYPE ... $self->{allowable_seqobjects}->{$self->{seqobjects}->{$so_id}->{seqobject_type}}\n";

    #warn "TEAM ... $self->{teamname}\n";
    #warn "---------------------------------------------------------------------------------------------\n";


    
    if (defined $self->{entities}->{$pe_id}->{internal_id}){
	##############################
	# If we do have an Internal_id
	##############################
	eval {
	    $self->{gtr_database}->do('so_proto_1.sql_lib::make_physical_entity_internal_id', [
											       $pe_id,
											       $self->{entities}->{$pe_id}->{entity_type},
											       $self->{teamname},
											       $self->{entities}->{$pe_id}->{internal_id}
											       ]);
	};
	
    } else {
	############################
	# If we have no internal_id
	############################
	eval {
	    $self->{gtr_database}->do('so_proto_1.sql_lib::make_physical_entity', [
										   $pe_id,
										   $self->{entities}->{$pe_id}->{entity_type},
										   $self->{teamname},
										   ]);
	};
    }




    if ($@ ne ''){
	warn $@;
	croak "Problems calling make_physical_entity !\n";
    }
    

    ##########################
    # Make a ENTITY_STATUS
    ##########################
    #warn "---------------------------------------------------------------------------------------------\n";
    #warn "Calling make_entity_status with ...\n";
    #warn "ID_ENTITY ... $pe_id\n";
    
    ##########################################################################################
    # Can we find the id_status value for an entity of this type of description 'Created' ?
    ##########################################################################################
    my $status = $self->{gtr_database}->get('so_proto_1.sql_lib::get_created_status_by_type', [$self->{entities}->{$pe_id}->{entity_type}]);
    $status = $status->[0]->[0];
    croak "gt_proto_api.pm create_new_entity method ERROR Cannot determine a Created status for entity type $self->{entities}->{$pe_id}->{entity_type}" unless (defined $status);

    #warn "ID_STATUS ... $status?\n";
    #warn "ID_ROLE ... $self->{id_role}\n";
    #warn "---------------------------------------------------------------------------------------------\n";


    eval {
	# ID_ENTITY, ID_STATUS, ID_ROLE
	$self->{gtr_database}->do('so_proto_1.sql_lib::make_entity_status', [
									     $pe_id,
									     $status,
									     $self->{id_role}
									     ]); 
    };

    if ($@ ne ''){
	warn $@;
	croak "Problems calling make_entity_status !\n";
    }


    
    ###############################################################
    # Foreach entity_contents we make, there may be many instances
    # of CONTENTS_INFO
    ###############################################################
    
    ##################################################################
    # Go through the populated content(wells) of this physical entity
    ##################################################################
    foreach my $content (keys %{ $self->{entities}->{$pe_id}->{well_info} }){

	################################
	# Make an ENTITY_CONTENTS entry
	################################
	#warn "---------------------------------------------------------------------------------------------\n";
	#warn "Calling make_entity_contents with ...\n";
	#warn "ID_ENTITY_CONTENT ... $self->{entities}->{$pe_id}->{well_info}->{$content}->{id_entity_content}\n";
	# where is this coming from ???

	#warn "ID_ENTITY ... $pe_id\n";
	#warn "ID_MAP ... $content\n";
	#warn "---------------------------------------------------------------------------------------------\n";

	eval {
	    $self->{gtr_database}->do('so_proto_1.sql_lib::make_entity_contents', [
										   $self->{entities}->{$pe_id}->{well_info}->{$content}->{id_entity_content},
										   $pe_id,
										   $content
										   ]);
	};
	
	if ($@ ne ''){
	    warn $@;
	    croak "Problems calling make_entity_contents !\n";
	}
	
	
	##############################################################
	# For this content_entry go through the associated meta-data
	##############################################################
	foreach my $info ( @{ $self->{entities}->{$pe_id}->{well_info}->{$content}->{contents_info}  } ) {
	    my ($key) = keys %$info;
	    my $value = $info->{$key};

	    #warn "KEY --- $key ... VALUE $value\n";

	    ################################
	    # Make an CONTENTS_INFO entry
	    ################################
	    #warn "---------------------------------------------------------------------------------------------\n";
	    #warn "Calling make_contents_info with ...\n";
	    #warn "ID_ENTITY_CONTENT ... $self->{entities}->{$pe_id}->{well_info}->{$content}->{id_entity_content}\n";
	    #warn "ID_CONTENT ... $value\n";
	    #warn "ID_CONTENT_TYPE ... $key\n";
	    #warn "---------------------------------------------------------------------------------------------\n";

	    eval {

		$self->{gtr_database}->do('so_proto_1.sql_lib::make_contents_info', [
										     $self->{entities}->{$pe_id}->{well_info}->{$content}->{id_entity_content},
										     $value,
										     $key
										     ]);
	    };

	    if ($@ ne ''){
		warn $@;
		croak "Problems calling make_contents_info !\n";
	    }   



	}
	
    }

}




sub start_timer {
    my ($self, $phrase) = @_;

    my ($pack, $file, $line) = caller;
    unless ($pack eq __PACKAGE__ && $file eq __FILE__){
	croak "gt_proto_api.pm start_timer method ERROR Invalid access to subroutine from $pack";
    }

    print STDERR "Started $phrase\n";
    $self->{start_timer} = gettimeofday;
    return;
}


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

    my ($pack, $file, $line) = caller;
    unless ($pack eq __PACKAGE__ && $file eq __FILE__){
	croak "gt_proto_api.pm stop_timer method ERROR Invalid access to subroutine from $pack";
    }

    $self->{stop_timer} = gettimeofday;

    my $total = ( ($self->{stop_timer}) - ($self->{start_timer}) );
    print STDERR "Finished $total\n";

    return;
}



1;
