package MOBY::Adaptor::moby::queryapi::mysql;

use strict;
use vars qw($AUTOLOAD @ISA);
use Carp;
use MOBY::Adaptor::moby::queryapi;
use DBI;
use DBD::mysql;

@ISA = qw{MOBY::Adaptor::moby::queryapi}; # implements the interface

{
	#Encapsulated class data
	
	#___________________________________________________________
	#ATTRIBUTES
    my %_attr_data = #     				DEFAULT    	ACCESSIBILITY
                  (
                   driver       =>  ["DBI:mysql",  'read/write'],
                   dbh		     =>  [undef,         'read/write'],
                   
                    );

   #_____________________________________________________________

    # METHODS, to operate on encapsulated class data

    # Is a specified object attribute accessible in a given mode
    sub _accessible  {
	my ($self, $attr, $mode) = @_;
	$_attr_data{$attr}[1] =~ /$mode/
    }

    # Classwide default value for a specified object attribute
    sub _default_for {
	my ($self, $attr) = @_;
	$_attr_data{$attr}[0];
    }

    # List of names of all specified object attributes
    sub _standard_keys {
	keys %_attr_data;
    }

	sub driver {
		my ($self, $arg) = @_;
		$self->{driver} = $arg if defined $arg;
		return $self->{driver};
	}
	sub dbh {
		my ($self, $arg) = @_;
		$self->{dbh} = $arg if defined $arg;
		return $self->{dbh};
	}

}

sub _getDBHandle {
    my ($ontology) = @_;
    my $CONF = MOBY::Config->new;
    my $adap = $CONF->getDataAdaptor(source => $ontology);
    return $adap->dbh;
}

sub new {
	my ($caller, %args) = @_;
	my $self = $caller->SUPER::new(%args);

	my $caller_is_obj = ref($caller);
    my $class = $caller_is_obj || $caller;

    foreach my $attrname ( $self->_standard_keys ) {
    	if (exists $args{$attrname} && defined $args{$attrname}) {
		$self->{$attrname} = $args{$attrname} }
    elsif ($caller_is_obj) {
		$self->{$attrname} = $caller->{$attrname} }
    else {
		$self->{$attrname} = $self->_default_for($attrname) }
    }

    return unless $self->driver;
    my $driver = $self->driver;  # inherited from the adaptorI (queryapi)
    my $username = $self->username;
    my $password = $self->password;
    my $port = $self->port;
    my $url = $self->url;
    my $dbname = $self->dbname;
    
    my ($dsn) = "$driver:$dbname:$url:$port";
    my $dbh = DBI->connect($dsn, $username, $password, {RaiseError => 1}) or die "can't connect to database";


    ##############################################################
    unless ($dbh) {
	    print STDERR "Couldn't connect to the datasource \n",($self->_dump()),"\n\n";
	    return undef;
    }
    
    $self->dbh($dbh);
    #############################################################

    return undef unless $self->dbh;
    return $self;
    
}

sub _add_condition{
	my ($statement, @params) = @_;
	my @bindvalues = ();
 	my $condition = "where ";
 
 	foreach my $param (@params ) 
 	{
		if (($param eq 'and') || ($param eq 'or'))
 		{
		 $condition .= $param . " ";
		}
 		else
 		{
		my %pair = %$param;

		for my $key (keys %pair)
			{			
	 			if (defined $pair{$key}){
				    #added a check for servicename to support case sensitivity
				    if ($key eq "servicename") {
					$condition .= $key . " LIKE binary ? ";
					push(@bindvalues, $pair{$key});
				    } elsif ($pair{$key} eq "IS NOT NULL"){
					$condition .= $key . " IS NOT NULL ";
				    } else {
				        $condition .= $key . " = ? ";
					push(@bindvalues, $pair{$key});
				    }
 				} else{
				    $condition .= $key . " IS NULL "
 				}
			}
		}
 	}
 	$statement .= $condition;
 	return ($statement, @bindvalues);
 }

# preforms query but returns a reference to an array containing hash references	
sub do_query{
	my ($dbh, $statement, @bindvalues) = @_;
	my $sth = $dbh -> prepare($statement);	
 	if (@bindvalues < 1)
 	{
 		$sth->execute;
 	}
 	else
 	{
 		$sth->execute(@bindvalues);
 	}
 	# returns an array of hash references
    my $arrayHashRef = $sth->fetchall_arrayref({});        
	return $arrayHashRef;
}

sub get_value{
	my ($key, @params) = @_;
	
	foreach my $param (@params )
	{
	    my %pair = %$param;
	    for my $tmp (keys %pair)
	    {
			if ($tmp eq $key){
				return $pair{$key};
			}
	    }
	}
}

sub _getSIIDFromLSID {
    my ($self, $lsid) = @_;
    my $dbh = $self->dbh;
    my $sth = $dbh->prepare("select service_instance_id from service_instance where lsid = ?");
    $sth->execute($lsid);
    my ($siid)  = $sth->fetchrow_array();
    return $siid;
}

# this should NOT retun a collection ID... needs more work...
# args passed in:  service_lsid
sub query_collection_input{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $serv_lsid = $args{'service_instance_lsid'};
	
	my $statement = "select
          collection_input_id,
          article_name 
          from collection_input as c, service_instance as si where si.service_instance_id = c.service_instance_id and si.lsid = ?";   
 	my $result = do_query($dbh, $statement, ($serv_lsid));
 	return $result;
}

# args passed in:  service_instance_lsid, article_name
sub insert_collection_input {
    my ($self, %args) = @_;
    my $article = $args{article_name};
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    
    $self->dbh->do("insert into collection_input (service_instance_id, article_name) values (?,?)", 
    undef, $siid, $article);
    my $id=$self->dbh->{mysql_insertid};
    return $id;
}

# pass in service_instance_lsid
sub delete_collection_input{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    
    my $statement = "delete from collection_input where service_instance_id = ?";
    $self->dbh->do( $statement, undef, $siid);
    
    if ($self->dbh->err){
	    return (1, $self->dbh->errstr);
    }
    else{
	    return 0;
    }	
}

# pass service_instance_lsid
sub query_collection_output{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;
    
    my $statement = "select
       collection_output_id,
       article_name,
       service_instance_id
       from collection_output where service_instance_id = ? ";       
    my $result = do_query($dbh, $statement, ($siid));
    return $result;
}

# pass service_instance_lsid, article_name
sub insert_collection_output {
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;
    $self->dbh->do("insert into collection_output (service_instance_id, article_name) values (?,?)", 
    undef, $siid,$args{'article_name'});
    my $id=$self->dbh->{mysql_insertid};
    return $id;
}

# pass argument service_instance_lsid
sub delete_collection_output{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;
    my $statement = "delete from collection_output where service_instance_id = ?";
    my @bindvalues = ();
    $dbh->do( $statement, undef, ($siid));

    if ($dbh->err){
	    return (1, $dbh->errstr);
    }
    else{
	    return 0;
    }	
}

# pass service_instance_lsid 
sub query_simple_input{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $collid = $args{collection_input_id};
    my $id_to_use = $siid?$siid:$collid;
    
    my $dbh = $self->dbh;
    
    my $statement = "select
      simple_input_id,
      object_type_uri,
      namespace_type_uris,
      article_name,
      service_instance_id,
      collection_input_id
      from simple_input where ";
      
	my $condition;
      $siid && ($condition = " service_instance_id = ? and collection_input_id IS NULL");
      $collid && ($condition = " collection_input_id = ?");
      $statement .= $condition;
      
    my $result = do_query($dbh, $statement, ($id_to_use));
    return $result;
}
	
# pass service_instance_lsid, object_type_uri, namespace_type_uris, article_name, collection_input_id
sub insert_simple_input {
    my ($self, %args) = @_;
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;	    
    $dbh->do("insert into simple_input
			     (object_type_uri,
			      namespace_type_uris,
			      article_name,
			      service_instance_id,
			      collection_input_id)
			     values (?,?,?,?,?)",
	    undef,
	    $args{'object_type_uri'},
	    $args{'namespace_type_uris'},
	    $args{'article_name'},
	    $siid,
	    $args{'collection_input_id'});
    my $id=$dbh->{mysql_insertid};
    return $id;
}

# pass service_instance_lsid
sub delete_simple_input{
    my ($self, %args) = @_;
    my $dbh = $self->dbh;
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my ($collid) = $args{collection_input_id};
    my $statement1; my $statement2;
    $siid && ($statement1 = "delete from simple_input where service_instance_id = ?");
    $collid && ($statement2 = "delete from simple_input where collection_input_id = ?");
    
    $siid && ($dbh->do( $statement1, undef,($siid)));
    $collid && ($dbh->do($statement2, undef,($collid)));
    if ($dbh->err){
	    return (1, $dbh->errstr);
    }
    else{
	    return 0;
    }			  
}

sub delete_inputs {  # this should replace all other delete_*_input
    my ($self, %args) = @_;
    my $dbh = $self->dbh;
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $result_ids = $self->query_collection_input(service_instance_lsid => $self->lsid);

    my $statement = "delete from simple_input where service_instance_id = ?";

    $dbh->do( $statement, undef,($siid));
    if ($dbh->err){
	    return (1, $dbh->errstr);
    }
    else{
	    return 0;
    }			  
    
}

sub delete_output {  # this should replace all other delete_*_output
    
}

# UGH this has to know too much bout the underlying database structure e.g. that one is null and other is full
# this problem is in MOBY::Central line 3321 3346 and 3374
#****** FIX
# send service_instance_lsid, collection_input_id
sub query_simple_output{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $collid = $args{collection_output_id};
    my $dbh = $self->dbh;
    my $id_to_use = $siid?$siid:$collid;
    
    my $statement = "select
      simple_output_id,
      object_type_uri,
      namespace_type_uris,
      article_name,
      service_instance_id,
      collection_output_id
      from simple_output where ";
      my $condition;
      $siid && ($condition = " service_instance_id = ? and collection_output_id IS NULL");
      $collid && ($condition = " collection_output_id = ?");
    $statement .= $condition;


    my $result = do_query($dbh, $statement, ($id_to_use));
    return $result;
}

# pass args service_instance_id and collection_output_id
sub insert_simple_output {
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;

    $dbh->do("insert into simple_output
			     (object_type_uri,
			      namespace_type_uris,
			      article_name,
			      service_instance_id,
			      collection_output_id)
			     values (?,?,?,?,?)",
	    undef,(
	    $args{'object_type_uri'},
	    $args{'namespace_type_uris'},
	    $args{'article_name'},
	    $siid,
	    $args{'collection_output_id'}));
    my $id=$dbh->{mysql_insertid};
    return $id;

}

# pass service_instance_id or collection_output_id
sub delete_simple_output{
    my ($self, %args) = @_;
    my $dbh = $self->dbh;
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my ($collid) = $args{collection_output_id};
    my $statement1; my $statement2;
    $siid && ($statement1 = "delete from simple_output where service_instance_id = ?");
    $collid && ($statement2 = "delete from simple_output where collection_output_id = ?");
    
    $siid && ($dbh->do( $statement1, undef,($siid)));
    $collid && ($dbh->do($statement2, undef,($collid)));
    if ($dbh->err){
	    return (1, $dbh->errstr);
    }
    else{
	    return 0;
    }			  
}	

# pass service_instance_lsid
sub query_secondary_input{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;
    
    my $statement = "select
      secondary_input_id,
      default_value,
      maximum_value,
      minimum_value,
      enum_value,
      datatype,
      article_name,
      description,
      service_instance_id
      from secondary_input where service_instance_id = ?";    
    my $result = do_query($dbh, $statement, ($siid));
    return $result;	
}

# pass default_value, maximum_value minimum_value enum_value datatype article_name service_instance_lsid
sub insert_secondary_input{
    my ($self, %args) = @_;	
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;		
    $dbh->do(q{insert into secondary_input (default_value,maximum_value,minimum_value,enum_value,datatype,article_name,description,service_instance_id) values (?,?,?,?,?,?,?,?)},
	    undef,
	    (
	       $args{'default_value'}, $args{'maximum_value'},
	       $args{'minimum_value'}, $args{'enum_value'},
	       $args{'datatype'}, $args{'article_name'}, $args{'description'},$siid)
    );
    return $dbh->{mysql_insertid};
}

# pass service_instance_lsid
sub delete_secondary_input{
    my ($self, %args) = @_;
    my ($siid) = $self->_getSIIDFromLSID($args{service_instance_lsid});
    my $dbh = $self->dbh;
    my $statement = "delete from secondary_input where service_instance_id=?";

    $dbh->do( $statement, undef, ($siid));    
    if ($dbh->err){
	    return (1, $dbh->errstr);
    }
    else{
	    return 0;
    }	
}


# receives argument "type", that may be either an LSID or a type term
sub query_object {
	my ($self, %args) = @_;
	my $type = $args{type};
	my $condition = "";
	if ($type =~ /^urn\:lsid/){
	    $condition = "where object_lsid = ?";
	} elsif ($type) {
	    $condition = "where object_type = ?";
	}
	my $statement = "select
          object_id,
          object_lsid,
          object_type,
          description,
          authority,
          contact_email
          from object $condition";

	my $dbh = _getDBHandle("mobyobject");
	my $result;
	if ($type){
	    $result = do_query($dbh, $statement, ($type));
	} else {
	    $result = do_query($dbh, $statement);
	}	    
 	return $result;		
}

# inserts a new tuple into object table
# pass object_type object_lsid description authority contact_email
sub insert_object{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;		
	$dbh->do("insert into object 
				 (object_type, 
				 object_lsid, 
				 description, 
				 authority,
				 contact_email)
				 values (?,?,?,?,?)",
		undef,
		$args{'object_type'},
		$args{'object_lsid'},
		$args{'description'},
		$args{'authority'},
		$args{'contact_email'});
	my $id=$dbh->{mysql_insertid};
	return $id;	
}

# pass 'type' which is either an LSID or a term
sub delete_object{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $term = $args{type};
	return 0 unless $term;
	my $result = $self->query_object(type => $term);
	my $row = shift(@$result);
	my $id = $row->{object_id};
	my $lsid = $row->{object_lsid};
	my $statement = "delete from object where object_lsid = ?";
	$dbh->do( $statement,undef, ($lsid) );
	
	$self->_delete_object_term2term(id => $id);
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}

# pass "type" here, should be an LSID, preferably...
sub query_object_term2term{
	my ($self, %args) = @_;
	my $type = $args{type};
	my $result = $self->query_object(type => $type);
	my $row = shift(@$result);
	my $id = $row->{object_id};
	return [{}] unless $id;
	my $dbh = $self->dbh;
	
	my $statement = "select
          assertion_id,
          relationship_type,
          object1_id,
          object2_id,
          object2_articlename
          from object_term2term where object2_id = ?";
 	my $result2 = do_query($dbh, $statement, ($id));
 	return $result2;			
}

# pass object1_type, object2_type, object2_articlename, relationship_type
sub insert_object_term2term{
	my ($self, %args) = @_;	
	my $type1 = $args{object1_type};
	my $result = $self->query_object(type => $type1);
	my $row = shift(@$result);
	my $id1 = $row->{object_id};
	my $type2 = $args{object2_type};
	$result = $self->query_object(type => $type2);
	$row = shift(@$result);
	my $id2 = $row->{object_id};
	my $relationship_type = $args{relationship_type};
	my $object2_articlename = $args{object2_articlename};

	my $dbh = $self->dbh;
	$dbh->do(
	    q{insert into object_term2term (relationship_type, object1_id, object2_id, object2_articlename) values (?,?,?,?)},
		undef,
		$relationship_type,
		$id1,
		$id2,
		$object2_articlename
	);
	
	return $dbh->{mysql_insertid};
}

# pass object 'type' as term or lsid
# this should be a private routine, not a public one.
# SHOULD NOT BE DOCUMENTED IN THE API
sub _delete_object_term2term{
	my ($self, %args) = @_;
	my $o1id = $args{id};
	return 0 unless defined($o1id);
	my $dbh = $self->dbh;	
	my $statement = "delete from object_term2term where object1_id=?";
	$dbh->do( $statement,undef, ($o1id));
	
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}

# pass servicename and authority_uri
# TODO added LIKE binary here
sub query_service_existence {
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;

	my $servicename = $args{'servicename'};
	my $authURI = $args{'authority_uri'};
	my $result = $self->_query_authority(authority_uri => $authURI);
	return 0 unless @$result[0];
	my $id = @$result[0]->{authority_id};
	return 0 unless $id;
	my $statement = "select
          service_instance_id,
          category,
          servicename,
          service_type_uri,
          authority_id,
          url,
          contact_email,
          authoritative,
          description,
		  signatureURL,
		  lsid 
          from service_instance where servicename LIKE binary ? and authority_id = ?";
 	my $final = do_query($dbh, $statement, ($servicename, $id));
 	if (@$final[0]){return 1} else {return 0}
	
}
# selects all the columns from service_instance table
# PAY ATTENTION to what this returns.  Not auth_id but auth_uri!!
# IMPORTANT: must use quotes for the keys of the hash (eg. 'authority.authority_uri' => $value )
sub query_service_instance {
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
    	
	my @args;
	while (my ($k, $v) = each %args){
	    push @args, ({$k => $v}, "and"); # format for the_add_condition subroutine
	    								 # but too bad won't be scalable for "or"
	}
    	
	if (keys(%args)){ pop @args;}  # remove final "and"
	
	my $statement = "select 
			service_instance_id, 
			category, 
			servicename, 
			service_type_uri, 
			authority.authority_uri, 
			url, 
			service_instance.contact_email, 
			authoritative, 
			description, 
			signatureURL,
			lsid 
			from service_instance, authority ";
        my @bindvalues;
 	($statement, @bindvalues) =_add_condition($statement, @args);
	if (keys(%args)){
	    $statement .= " and authority.authority_id = service_instance.authority_id";
	} else {
	    $statement .= " where authority.authority_id = service_instance.authority_id";
	}
 	my $final = do_query($dbh, $statement, @bindvalues);
 	return $final;
}

# custom query for Moby::Central.pm->findService()
# hmmmmmmm....  I'm not sure that this routine should exist...
# it is redundant to the routine above, if the routine above were executed
# multiple times.  I think that is the more correct (though less efficient)
# way to go, since it is "scalable" to every possible underlying data source
# ********FIX  change this later...
sub match_service_type_uri{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $uri_list = $args{'service_type_uri'};
	my $statement = "select service_instance_id,category, servicename, service_type_uri, authority_id, url, contact_email, authoritative, description, signatureURL, lsid from service_instance where service_type_uri in ($uri_list)";
	my @bindvalues = ();	
	my $result = do_query($dbh, $statement, @bindvalues);
	return $result;
}

# passs........  blah blah..... 
sub insert_service_instance {
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $authority_id;
	if ($args{'authority_uri'}){ # need to transform URI to a row ID
	    my $result = $self->_query_authority(authority_uri => $args{'authority_uri'});
	    return undef unless @$result[0];
	    $authority_id = @$result[0]->{authority_id};
	    return undef unless $authority_id;
	}	

	$dbh->do(q{insert into service_instance (category, servicename, service_type_uri, authority_id, url, contact_email, authoritative, description, signatureURL, lsid) values (?,?,?,?,?,?,?,?,?,?)},
				 undef,(
				 $args{'category'},
				 $args{'servicename'},
				 $args{'service_type_uri'},
				 $authority_id,
				 $args{'url'},
				 $args{'contact_email'},
				 $args{'authoritative'},
				 $args{'description'},
				 $args{'signatureURL'},
				 $args{'lsid'}));
	
	my $id = $dbh->{mysql_insertid};
	return $id;
}

# pass service_instance_lsid
sub delete_service_instance{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $statement = "delete from service_instance where lsid = ?";
	$dbh->do( $statement,undef, ($args{service_instance_lsid}) );
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}
	
# Selects all columns EXCEPT authority_id
# pass authority_uri
sub query_authority {
	my ($self, %args) = @_;
	my $authURI = $args{authority_uri};
	my $dbh = $self->dbh;
	
	my $statement = "select
          authority_common_name,
          authority_uri,
          contact_email
          from authority where authority_uri = ?";
 	my $result = do_query($dbh, $statement, ($authURI));
 	return $result;
}

# Selects all columns including authority_id
# pass authority_uri.  NOTE THAT THIS IS A PRIVATE ROUTINE
# SHOULD NOT BE DOCUMENTED IN THE API
sub _query_authority {
	my ($self, %args) = @_;
	my $authURI = $args{authority_uri};
	my $dbh = $self->dbh;
	
	my $statement = "select
          authority_common_name,
          authority_uri,
          authority_id,
          contact_email
          from authority where authority_uri = ?";
 	my $result = do_query($dbh, $statement, ($authURI));
 	return $result;
}

# custom query routine used in Moby::Central.pm -> retrieveServiceProviders()
# no args passed
sub get_all_authorities{
	my ($self, @args) = @_;
	my $dbh = $self->dbh;
	my $statement = "select distinct authority.authority_uri from service_instance right join authority on authority.authority_id = service_instance.authority_id  where servicename IS NOT NULL order by authority.authority_uri;";
	my @bindvalues = ();
	my $result = do_query($dbh, $statement, @bindvalues);
	return $result;
}

# pass authority_common_name, authority_uri, contact_email, return ID of some sort
sub insert_authority{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;		
	$dbh->do("insert into authority 
				 (authority_common_name,
				  authority_uri,
				  contact_email)
				 values (?,?,?)",
		undef,
		($args{'authority_common_name'},
		$args{'authority_uri'},
		$args{'contact_email'}));
	my $id = $dbh->{mysql_insertid};
	return $id;
}

# pass service_type, as term or LSID
sub query_service{
	my ($self, %args) = @_;
	my $type = $args{type}||"";
	my $condition = "";
	if ($type =~ /^urn\:lsid/){
	    $condition = "where service_lsid = ?";
	} elsif ($type) {
	    $condition = "where service_type = ?";
	} else {
	    $condition = "";
	}
	
	my $dbh = _getDBHandle("mobyservice");

	my $statement = "select
	  service_id, 
          service_lsid,
          service_type,
          description,
          authority,
          contact_email
	  from
	  service 
          $condition";
	my $result;
	if ($type){
	    $result = do_query($dbh, $statement, ($type));
	} else {
	    $result = do_query($dbh, $statement);
	}
 	return $result;		
}

sub new_query_service{
	my ($self, %args) = @_;
	my $type = $args{type}||"";
	my $condition = "";
	if ($type =~ /^urn\:lsid/){
	    $condition = "where s1.service_lsid = ?";
	} elsif ($type) {
	    $condition = "where s1.service_type = ?";
	} else {
	    $condition = "";
	}
	
	my $dbh = _getDBHandle("mobyservice");

	my $statement = "select
	  s1.service_id as service_id, 
          s1.service_lsid as service_lsid,
          s1.service_type as service_type,
          s1.description as description,
          s1.authority as authority,
          s1.contact_email as contact_email,
	  s2.service_type as parent_type,
	  s2.service_lsid as parent_lsid  
          from
	  service as s1
	  left join service_term2term as t
	    on s1.service_id= t.service1_id
	left join service as s2
	on s2.service_id=t.service2_id
        $condition";
	my $result;
	if ($type){
	    $result = do_query($dbh, $statement, ($type));
	} else {
	    $result = do_query($dbh, $statement);
	}
 	return $result;		
}



# pass in ....
sub insert_service{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	$dbh->do(q{insert into service (service_type, service_lsid, description, authority, contact_email) values (?,?,?,?,?)},
		undef,
		(
		   $args{'service_type'}, $args{'service_lsid'}, $args{'description'},
		   $args{'authority'}, $args{'contact_email'}
		)
	);
	return $dbh->{mysql_insertid};
}

# pass in 'type' as a term or lsid
sub delete_service{
	my ($self, %args) = @_;	
	my $type = $args{type};
	my $result = $self->query_service(type => $type);
	my $row = shift(@$result);
	my $id = $row->{service_id};
	my $lsid = $row->{service_lsid};
	return 0 unless $lsid;
	my $dbh = $self->dbh;
	my $statement = "delete from service where service_lsid = ?";
	$dbh->do( $statement, undef, ($lsid));
	$self->_delete_service_term2term(id => $id);
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}

sub query_service_term2term{
    	my ($self, %args) = @_;
	my $type = $args{type};
	my $result = $self->query_service(type => $type);
	my $row = shift(@$result);
	my $id = $row->{service_id};
	return [{}] unless $id;
	my $dbh = $self->dbh;
	
	my $statement = "select
          assertion_id,
          relationship_type,
          service1_id,
          service2_id 
          from service_term2term where service2_id = ?";
 	my $result2 = do_query($dbh, $statement, ($id));
 	return $result2;			
}

#pass relationshiptype, servce1_type, service2_type
sub insert_service_term2term{
	my ($self, %args) = @_;	
	my $type1 = $args{service1_type};
	my $result = $self->query_service(type => $type1);
	my $row = shift(@$result);
	my $id1 = $row->{service_id};
	my $type2 = $args{service2_type};
	$result = $self->query_service(type => $type2);
	$row = shift(@$result);
	my $id2 = $row->{service_id};
	my $relationship_type = $args{relationship_type};

	my $dbh = $self->dbh;
	$dbh->do(q{insert into service_term2term (relationship_type, service1_id, service2_id) values (?,?,?)},
		undef,
		($relationship_type,
		$id1,
		$id2)
	);
	
	return $dbh->{mysql_insertid};
}


# NOTE THAT THIS IS A PRIVATE FUNCTION AND SHOULD
# NOT BE DOCUMENTED IN THE API.  
sub _delete_service_term2term{
	my ($self, %args) = @_;
	my $id = $args{id};
	return 0 unless (defined($id));
	my $dbh = $self->dbh;	
	my $statement = "delete from service_term2term where service1_id=?";
	$dbh->do( $statement,undef, ($id));	
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}


sub query_relationship{
	my ($self, %args) = @_;
	my $type = $args{type} || "";
#	return [{}] unless $type;
	my $condition = "";
	if ($type =~ /^urn\:lsid/){
	    $condition = "  relationship_lsid = ? and ";
	} elsif ($type) {
	    $condition = "  relationship_type = ? and ";
	}
	my $ont = $args{ontology};
	
	my $dbh = $self->dbh;
	
	my $statement = "select
          relationship_id,
          relationship_lsid,
          relationship_type,
          container,
          description,
          authority,
          contact_email,
          ontology
          from relationship where $condition ontology = ?";
 	
	if ($type){
	    return do_query($dbh, $statement, ($type, $ont));
	} else {
	    return do_query($dbh, $statement, ($ont));
	}
}

sub query_namespace{
	my ($self, %args) = @_;	
	my $type = $args{type};
	my $condition = "";
	if ($type =~ /^urn\:lsid/){
	    $condition = " where namespace_lsid = ?";
	} elsif ($type) {
	    $condition = " where namespace_type = ?";
	} else {
	    $condition = "";
	}
	
	my $dbh = _getDBHandle("mobynamespace");
	
	my $statement = "select
          namespace_id,
          namespace_lsid,
          namespace_type,
          description,
          authority,
          contact_email
          from namespace $condition";
	my $result;
	if ($type){
	    $result = do_query($dbh, $statement, ($type));
	} else {
	    $result = do_query($dbh, $statement);
	}
 	return $result;	
}


sub insert_namespace{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	$dbh->do(q{insert into namespace (namespace_type, namespace_lsid, description, authority,contact_email) values (?,?,?,?,?)},
		undef,
		(
		   $args{'namespace_type'}, $args{'namespace_lsid'},$args{'description'},$args{'authority'},$args{'contact_email'}
		)
	);
	return $dbh->{mysql_insertid};
}

# pass namesapce_lsid
sub delete_namespace{
	my ($self, %args) = @_;	
	my $type = $args{type};
	my $result = $self->query_namespace(type => $type);
	my $row = shift(@$result);
	my $id = $row->{namespace_id};
	my $lsid = $row->{namespace_lsid};
	return 0 unless $lsid;
	my $dbh = $self->dbh;
	my $statement = "delete from namespace where namespace_lsid = ?";
	$dbh->do( $statement, undef, ($lsid));
	$self->_delete_namespace_term2term(id => $id);
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}

sub query_namespace_term2term{
    	my ($self, %args) = @_;
	my $type = $args{type};
	my $result = $self->query_namespace(type => $type);
	my $row = shift(@$result);
	my $id = $row->{namespace_id};
	return [{}] unless $id;
	my $dbh = $self->dbh;
	
	my $statement = "select
          assertion_id,
          relationship_type,
          namespace1_id,
          namespace2_id
          from namespace_term2term where namespace2_id = ?";
 	my $result2 = do_query($dbh, $statement, ($id));
 	return $result2;
}

# PRIVATE, NOT PART OF API!
sub _delete_namespace_term2term{
	my ($self, %args) = @_;
	my $id = $args{id};
	return 0 unless defined($id);
	my $dbh = $self->dbh;	
	my $statement = "delete from namespace_term2term where namespace1_id=?";
	$dbh->do( $statement,undef, ($id));	
	if ($dbh->err){
		return (1, $dbh->errstr);
	}
	else{
		return 0;
	}
}
# pass type as LSID or term
sub check_object_usage{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;	
	my $errorMsg = 1;
	my $type = $args{type};
	return 0 unless $type;
	my $result = $self->query_object(type => $type);
	my $row = shift @$result;
	my $lsid = $row->{object_lsid};
	
	my ($id) = $dbh->selectrow_array(q{select service_instance.service_instance_id from service_instance natural join simple_input where object_type_uri = ?},
		undef, $lsid
	);
	return $errorMsg
	  if ($id);
	  
	($id) = $dbh->selectrow_array(q{select service_instance.service_instance_id from service_instance natural join simple_output where object_type_uri = ?},
		undef, $lsid
	);
	return $errorMsg
	  if ($id);
	  
	($id) = $dbh->selectrow_array(q{select service_instance.service_instance_id from service_instance natural join collection_input natural join simple_input where object_type_uri = ?},
		undef, $lsid
	);
	return $errorMsg
	  if ($id);
	  
	($id) = $dbh->selectrow_array(q{select service_instance.service_instance_id from service_instance natural join collection_output natural join simple_output where object_type_uri = ?},
		undef, $lsid
	);
	return $errorMsg
	  if ($id);	  
	  
	return 0;
}

# custom query routine for Moby::Central.pm -> deregisterNamespace()
sub check_namespace_usage{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;	
	my $errorMsg = 1;
	my $type = $args{type};
	return 0 unless $type;
	my $result = $self->query_namespace(type => $type);
	my $row = shift @$result;
	my $lsid = $row->{namespace_lsid};
		
        my $sth = $dbh->prepare("select service_instance.service_instance_id, namespace_type_uris from service_instance natural join simple_input where INSTR(namespace_type_uris,'$lsid')"
	  );
	$sth->execute;

	while ( my ( $id, $ns ) = $sth->fetchrow_array() ) {
		my @nss = split ",", $ns;
		foreach (@nss) {
			$_ =~ s/\s//g;
			my $errstr = "Namespace Type $type ($_) is used by a service (service ID number $id) and may not be deregistered";
			return (1, $errstr)
			  if ( $_ eq $lsid );
		}
	}
	$sth = $dbh->prepare("select service_instance.service_instance_id, namespace_type_uris from service_instance natural join simple_output where INSTR(namespace_type_uris,'$lsid')"
	  );
	$sth->execute;
	while ( my ( $id, $ns ) = $sth->fetchrow_array() ) {
		my @nss = split ",", $ns;
		foreach (@nss) {
			$_ =~ s/\s//g;
			my $errstr = "Namespace Type $type ($_) is used by a service (service ID number $id) and may not be deregistered";
			return (1, $errstr)
			  if ( $_ eq $lsid );
		}
	}
	$sth =
	  $dbh->prepare("select service_instance.service_instance_id, namespace_type_uris from service_instance natural join collection_input natural join simple_input where INSTR(namespace_type_uris, '$lsid')"
	  );
	$sth->execute;
	while ( my ( $id, $ns ) = $sth->fetchrow_array() ) {
		my @nss = split ",", $ns;
		foreach (@nss) {
			$_ =~ s/\s//g;
			my $errstr = "Namespace Type $type ($_) is used by a service (service ID number $id) and may not be deregistered";
			return (1, $errstr)
			  if ( $_ eq $lsid );
		}
	}
	$sth =
	  $dbh->prepare("select service_instance.service_instance_id, namespace_type_uris from service_instance natural join collection_output natural join simple_output where INSTR(namespace_type_uris, '$lsid')"
	  );
	$sth->execute;
	while ( my ( $id, $ns ) = $sth->fetchrow_array() ) {
		my @nss = split ",", $ns;
		foreach (@nss) {
			$_ =~ s/\s//g;
			my $errstr = "Namespace Type $type ($_) is used by a service (service ID number $id) and may not be deregistered";
			return (1, $errstr)
			  if ( $_ eq $lsid );
		}
	}		
	return (0, "");
}

# custom query routine for Moby::Central.pm -> findService()	
sub check_keywords{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;		
	my $param = $args{keywords};
	return ([{}]) unless (ref($param) =~ /ARRAY/);
	my @keywords = @$param;
	#my %findme = %$param;
	my $searchstring;
	foreach my $kw ( @keywords ) {
			$kw =~ s/\*//g;
			$kw = $dbh->quote("%$kw%");
			$searchstring .= " OR description like $kw ";
		}
		$searchstring =~ s/OR//;    # remove just the first OR in the longer statement

	my $statement = "select service_instance_id,category, servicename, service_type_uri, authority_id, url, contact_email, authoritative, description, signatureURL, lsid from service_instance where $searchstring";
	my @bindvalues = ();
	
	my $ids = do_query($dbh, $statement, @bindvalues);		  
	return ($ids);
}	
	
# custom query subroutine for Moby::Central.pm->_searchForSimple()
sub find_by_simple{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;	
	my $inout = $args{'inout'};
	my $ancestor_string = $args{'ancestor_string'};
	my $namespaceURIs = $args{'namespaceURIs'};
	
	my $query ="select service_instance_id, namespace_type_uris from simple_$inout where object_type_uri in ($ancestor_string) and collection_${inout}_id IS NULL "
	  ;    # if service_instance_id is null then it must be a collection input.
	my $nsquery;
	foreach my $ns ( @{$namespaceURIs} ) {    # namespaces are already URI's
		$nsquery .= " OR INSTR(namespace_type_uris, '$ns') ";
	}
	if ($nsquery) {
		$nsquery =~ s/OR//;                   # just the first
		$nsquery .= " OR namespace_type_uris IS NULL";
		$query   .= " AND ($nsquery) ";
	}
	
	my $result = do_query($dbh, $query, ());
 	return $result;
}

# custom query subroutine for Moby::Central.pm->_searchForCollection()
sub find_by_collection{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;	
	my $inout = $args{'inout'};
	my $objectURI = $args{'objectURI'};
	my $namespaceURIs = $args{'namespaceURIs'};
	
	my $query = "select
			c.service_instance_id,
			s.namespace_type_uris
		from
			simple_$inout as s,
			collection_$inout as c
		where
			s.collection_${inout}_id IS NOT NULL
		AND s.collection_${inout}_id = c.collection_${inout}_id
		AND object_type_uri = '$objectURI' ";
	my $nsquery;
		foreach my $ns ( @{$namespaceURIs} ) {    # namespaces are already URI's
			$nsquery .= " OR INSTR(namespace_type_uris, '$ns') ";
		}
		if ($nsquery) {
			$nsquery =~ s/^\sOR//;                # just the first
			$nsquery .= " OR namespace_type_uris IS NULL";
			$query   .= " AND ($nsquery) ";                 # add the AND clause
		}
		
	my $result = do_query($dbh, $query, ());
 	return $result;
}

# custom query subroutine for Moby::Central.pm->RetrieveServiceNames
sub get_service_names{		
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;	
	my $statement = "select authority_uri, servicename, lsid from authority as a, service_instance as s where s.authority_id = a.authority_id";	
	my @bindvalues = ();
	
    my $result = do_query($dbh, $statement, @bindvalues);
	return $result; 
}

# custom query for Moby::Central.pm->_flatten
sub get_parent_terms{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	
	my $type_id = $args{'relationship_type_id'};
	my $statement = "
	select
		OE1.term
	from
		OntologyEntry as OE1,
		OntologyEntry as OE2,
		Term2Term as TT
	where
		ontologyentry2_id = OE2.id
		and ontologyentry1_id = OE1.id
		and relationship_type_id = $type_id
		and OE2.term = ?";
		
	my @bindvalues = ();
	push(@bindvalues, $args{'term'});
	
	my $result = do_query($dbh, $statement, @bindvalues);
	return $result;
}

# custom query subroutine for selecting from object_term2term and object tables
# used in Moby::OntologyServer.pm->retrieveObject()
sub get_object_relationships{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $type = $args{type};
	return 0 unless $type;
	my $result = $self->query_object(type => $type);
	my $row = shift @$result;
	my $id = $row->{object_id};

	my $statement = "select 
	relationship_type,
	object_type,
	object_lsid,
	description,
	authority,
	contact_email,
	object2_articlename 
	from object_term2term, object 
	where object1_id = ? and object2_id = object_id";
	
	my $result2 = do_query($dbh, $statement, ($id));
	return $result2;
}

# relationship query for any table used in Moby::OntologyServer->_doRelationshipQuery()
# note: returns a reference to an array containing ARRAY references
sub get_relationship{
	my ($self, %args) = @_;	
	my $dbh = $self->dbh;
	my $direction = $args{'direction'};
	my $ontology = $args{'ontology'};
	my $relationship = $args{'relationship'}; # this is assumed to be an LSID

	my $type = $args{'term'};
	return 0 unless $type;
	my $lsid;
	if ($ontology eq "service"){
	    my $result = $self->query_service(type => $type);
	    my $row = shift @$result;
	    $lsid = $row->{service_lsid};
	} else {
	    my $result = $self->query_object(type => $type);
	    my $row = shift @$result;
	    $lsid = $row->{object_lsid};
	}
	my $defs;
	my $extra_columns;
	$extra_columns = ", relationship_type ";
	if ($ontology eq "object"){$extra_columns .=", object2_articlename ";}
	if ( $direction eq 'root' ) {
		unless ( defined $relationship ) {
			$defs = $self->dbh->selectall_arrayref( "
            select distinct s2.${ontology}_lsid $extra_columns from
                ${ontology}_term2term as t2t,
                $ontology as s1,
                $ontology as s2  
            where
                s1.${ontology}_id = t2t.${ontology}1_id and
                s2.${ontology}_id = t2t.${ontology}2_id and
                s1.${ontology}_lsid = ?", undef, $lsid );    # ")
		} else {
			$defs = $self->dbh->selectall_arrayref( "
            select distinct s2.${ontology}_lsid $extra_columns from
                ${ontology}_term2term as t2t,
                $ontology as s1,
                $ontology as s2  
            where
                relationship_type = ? and 
                s1.${ontology}_id = t2t.${ontology}1_id and
                s2.${ontology}_id = t2t.${ontology}2_id and
                s1.${ontology}_lsid = ?", undef, $relationship, $lsid );    # ")
		}
	} else {
		unless ( defined $relationship ) {
			$defs = $self->dbh->selectall_arrayref( "
            select distinct s1.${ontology}_lsid $extra_columns from
                ${ontology}_term2term as t2t,
                $ontology as s1,
                $ontology as s2  
            where
                s1.${ontology}_id = t2t.${ontology}1_id and
                s2.${ontology}_id = t2t.${ontology}2_id and
                s2.${ontology}_lsid = ?", undef, $lsid);                   # ")
		} else {
			$defs = $self->dbh->selectall_arrayref( "
            select distinct s1.${ontology}_lsid $extra_columns from
                ${ontology}_term2term as t2t,
                $ontology as s1,
                $ontology as s2  
            where
                relationship_type = ? and 
                s1.${ontology}_id = t2t.${ontology}1_id and
                s2.${ontology}_id = t2t.${ontology}2_id and
                s2.${ontology}_lsid = ?", undef, $relationship, $lsid );    # ")
		}
	}
	return $defs;
}

# Get all relationships in the queried database in one go.  The
# complete table ${ontology}_term2term is transferred into a hash
# whose reference is finally returned.  Important: note that the hash
# is built 'direction aware', that is for objects 'object1_id' is used
# as key when direction is 'root' and 'object2_id' as value. Vice
# versa for the 'leaves' direction.  Likewise for services.
# Returns a hash reference.
sub get_all_relationships {

  my ($self, %args) = @_;
  my $direction = $args{'direction'};
  my $ontology = $args{'ontology'};
  # my $relationship = $args{'relationship'}; # has to be lsid!

  my $relHash;
  my $dbh = _getDBHandle("moby$ontology");

  my $statement = "select ${ontology}1_id, ${ontology}2_id, relationship_type";
  $statement .= ", object2_articlename, assertion_id " if $ontology eq 'object';
  $statement .= " from ${ontology}_term2term";
  # my $relationship_lsid = "urn:lsid:biomoby.org:${ontology}relation:isa";
  my $defs = $dbh->selectall_arrayref($statement);

  return {} unless @$defs;
  foreach my $def (@$defs) {
    my $relationship = $def->[2];
    if ( $relationship =~ /has/i ) {
      # HAS or HASA
      # >1 has/hasa child possible; also store articlename and assertion_id
      # hash structure: $relHash->{has/a-lsid}->{object1_id}->[object2_id,articlename,assertion_id]
      push @{$relHash->{$relationship}->{$def->[0]}}, [$def->[1],$def->[3],$def->[4]] if $direction eq 'root';
      push @{$relHash->{$relationship}->{$def->[1]}}, [$def->[0],$def->[3],$def->[4]] if $direction eq 'leaves';
    }
    elsif ( $relationship =~ /isa/i ) {
      # ISA
      push @{$relHash->{$relationship}->{$def->[1]}}, $def->[0] if $direction eq 'leaves'; # >1 child possible!
      $relHash->{$relationship}->{$def->[0]} = $def->[1] if $direction eq 'root'; # no multi parents!
    }
    else { return {}; }
  }
  return $relHash;
}

# retrieve details for a number of entities from table $ontology
# represented by a list of ${ontology}_id's;
# used in MOBY::OntologyServer::Relationships
sub get_details_for_id_list {
  my ($self, $ontology, $fields, $idList) = @_;

  return {} unless @$idList;
  return {} unless @$fields;

  my $dbh = _getDBHandle("moby$ontology");
  my $result = {};

  # avoid errors due to wrong field names:
  my %existingFields;
  my @queryFields = ();
  my $resArray = $dbh->selectall_arrayref("SHOW COLUMNS FROM $ontology");
  foreach my $row ( @$resArray ) {
    $existingFields{$row->[0]}++;
  }
  foreach my $field ( @$fields ) {
    next if $field eq "${ontology}_id";
    if ( exists $existingFields{$field} ) {
      push @queryFields, $field;
    }
    else {
      warn "Requested field $field does not exist in table $ontology!";
    }
  }

  #
  my $statement = "select ${ontology}_id, ". join(",", @queryFields). 
    " from $ontology where ${ontology}_id in (" .
      join(",", @$idList) . ")";
  $resArray = $dbh->selectall_arrayref($statement);
  foreach my $row ( @$resArray ) {
    my $entityId = shift @$row;
    foreach my $field (@queryFields) {
      my $value = shift @$row;
      $result->{$entityId}->{$field} = $value ? $value : '';
    }
  }
  return $result;
}

sub _checkURI {
	
#	my $uri = "http://www.ics.uci.edu/pub/ietf/uri/#Related";
#print "$1, $2, $3, $4, $5, $6, $7, $8, $9" if
#  $uri =~ m{^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?};
#
#The license for this recipe is available here.
#
#Discussion:
#
#If the match is successful, a URL such as
#
#http://www.ics.uci.edu/pub/ietf/uri/#Related
#
#will be broken down into the following group match variables:
#
#$1 = http:
#$2 = http
#$3 = //www.ics.uci.edu
#$4 = www.ics.uci.edu
#$5 = /pub/ietf/uri/
#$6 =
#$7 =
#$8 = #Related
#$9 = Related
#
#In general, this regular expression breaks a URI down into the following parts,
#as defined in the RFC:
#
#scheme = $2
#authority = $4
#path = $5
#query = $7
#fragment = $9

}

sub DESTROY {}

1;
