package Pod::WSDL::Method;

use strict;
use warnings;
use Pod::WSDL::Param;
use Pod::WSDL::Fault;
use Pod::WSDL::Return;
use Pod::WSDL::Doc;
use Pod::WSDL::Writer;
use Pod::WSDL::Utils qw(:writexml :namespaces :messages);
use Pod::WSDL::AUTOLOAD;

our $VERSION = "0.05";
our @ISA = qw/Pod::WSDL::AUTOLOAD/;

our $EMPTY_MESSAGE_NAME    = 'empty';
our $REQUEST_SUFFIX_NAME   = 'Request';
our $RESPONSE_SUFFIX_NAME  = 'Response';
our $RETURN_SUFFIX_NAME    = 'Return';
our $TARGET_NS_DECL        = 'tns1';

our %FORBIDDEN_METHODS = (
	name     => {get => 1, set =>  0},
	params   => {get => 1, set =>  0},
	doc      => {get => 1, set =>  1},
	return   => {get => 1, set =>  1},
	faults   => {get => 1, set =>  0},
	oneway   => {get => 1, set =>  1},
	writer   => {get => 0, set =>  0},
);

sub new {
	my ($pkg, %data) = @_;
	
	die "A method needs a name, died"   unless defined $data{name};
	die "A method needs a writer, died" unless defined $data{writer} and ref $data{writer} eq 'Pod::WSDL::Writer';
	
	bless {
		_name                => $data{name},
		_params              => $data{params} || [],
		_return              => $data{return},
		_doc                 => $data{doc} || new Pod::WSDL::Doc('_DOC'),
		_faults              => $data{faults} || [],
		_oneway              => $data{oneWay} || 0,
		_writer              => $data{writer},
		_emptyMessageWritten => 0,
	}, $pkg;
}

sub addParam {
	push @{$_[0]->{_params}}, $_[1] if defined $_[1];
}

sub addFault {
	push @{$_[0]->{_faults}}, $_[1] if defined $_[1];
}

sub requestName {
	return $_[0]->name . $REQUEST_SUFFIX_NAME;
}

sub responseName {
	return $_[0]->name . $RESPONSE_SUFFIX_NAME;
}

sub writeMessages {
	my $me      = shift;
	my $types   = shift;
	my $style   = shift;
	my $wrapped = shift;
	
	$me->_writeMessageRequestElem($types, $style, $wrapped);
	$me->writer->wrNewLine;

	unless ($me->oneway) {
		if ($me->return) {
			$me->_writeMessageResponseElem($types, $style, $wrapped);
			$me->writer->wrNewLine;
		} else {
			unless ($me->writer->emptyMessageWritten) {
				$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:message', name => $EMPTY_MESSAGE_NAME);
				$me->writer->registerWrittenEmptyMessage;
				$me->writer->wrNewLine;
			}
		}
	}
	
	for my $fault (@{$me->faults}) {
		next if $me->writer->faultMessageWritten($fault->wsdlName);
		
		$me->_writeMessageFaultElem($fault->wsdlName, $style, $wrapped);
		$me->writer->registerWrittenFaultMessage($fault->wsdlName);
		$me->writer->wrNewLine;
	}
}

sub writePortTypeOperation {
	my $me = shift;
	
	my $name = $me->name;
	my $paramOrder = '';
	
	for my $param (@{$me->params}) {
		$paramOrder .= $param->name . ' ';
	}

	$paramOrder =~ s/\s+$//;
	
	my $inputName  = $name . $REQUEST_SUFFIX_NAME;
	my $outputName = $name . $RESPONSE_SUFFIX_NAME;

    # maintain param order, name always first
    # if no params, don't send and element with that name
    my @p_order = $paramOrder ? ('parameterOrder', $paramOrder) : () ;

    $me->writer->wrElem($START_PREFIX_NAME, 'wsdl:operation', name => $name, @p_order);
	$me->writer->wrDoc($me->doc->descr);
	$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:input', message => "$IMPL_NS_DECL:$inputName", name => $inputName);
	
	# if method has no return, we treat it as one-way operation
	unless ($me->oneway) {
		if ($me->return) {
			$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:output', message => "$IMPL_NS_DECL:$outputName", name => $outputName);
		} else {
			$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:output', message => "$IMPL_NS_DECL:$EMPTY_MESSAGE_NAME");
		}
	}

	my $elemType;

	# write methods faults
	for my $fault (@{$me->faults}) {
		
		# if we want documentation and have some documentation ...
		if ($fault->descr and $me->writer->withDocumentation) {
			$elemType = $START_PREFIX_NAME;
		} else {
			$elemType = $EMPTY_PREFIX_NAME;
		}
		
		$me->writer->wrElem($elemType, "wsdl:fault", message => "$IMPL_NS_DECL:" . $fault->wsdlName, name => $fault->wsdlName);
		
		# only, if with documentation
		if ($elemType eq $START_PREFIX_NAME) {
			$me->writer->wrDoc($fault->descr);
			$me->writer->wrElem($END_PREFIX_NAME, "wsdl:fault");
		}
	}

	$me->writer->wrElem($END_PREFIX_NAME, 'wsdl:operation');
}

sub _writeMessageRequestElem {
	my $me      = shift;
	my $types   = shift;
	my $style   = shift;
	my $wrapped = shift;

	$me->writer->wrElem($START_PREFIX_NAME, 'wsdl:message', name => $me->requestName);
	
	if ($wrapped) {
		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:part', name => 'parameters', element => $me->requestName);
	} else {
		for my $param (@{$me->params}) {
			$me->_writePartElem($param->name, $param->type, $param->array, $param->descr, $style, 0, $types->{$param->type}) if $param->paramType =~ /^(INOUT|OUT|IN)$/;
		}
	}
	
	$me->writer->wrElem($END_PREFIX_NAME, 'wsdl:message');
}

sub _writeMessageResponseElem {
	my $me      = shift;
	my $types   = shift;
	my $style   = shift;
	my $wrapped = shift;

	$me->writer->wrElem($START_PREFIX_NAME, 'wsdl:message', name => $me->responseName);

	if ($wrapped) {
		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:part', name => 'parameters', element => $me->responseName);
	} else {
		for my $param (@{$me->params}) {
			$me->_writePartElem($param->name, $param->type, $param->array, $param->descr, $style, 0, $types->{$param->type}) if $param->paramType =~ /^(INOUT|OUT)?$/;
		}
	
		if (defined $me->return) {
			$me->_writePartElem($me->name . $RETURN_SUFFIX_NAME, $me->return->type, $me->return->array, $me->return->descr, $style, 1, $types->{$me->return->type});
		}
	}
	
	$me->writer->wrElem($END_PREFIX_NAME, 'wsdl:message');
}

sub _writeMessageFaultElem {
	my $me      = shift;
	my $name    = shift;
	my $style   = shift;
	my $wrapped = shift;

	my %attrs = (name => $FAULT_NAME);
	
	if ($style eq $RPC_STYLE) {
		$attrs{type} = "$TARGET_NS_DECL:$name";
	} elsif ($style eq $DOCUMENT_STYLE) {
		$attrs{element} = $name . $MESSAGE_PART;
	}

	$me->writer->wrElem($START_PREFIX_NAME, 'wsdl:message', name => $name);
	$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:part', %attrs);
	$me->writer->wrElem($END_PREFIX_NAME, 'wsdl:message');
}

sub _writePartElem {
	my $me       = shift;
	my $name     = shift;
	my $type     = shift;
	my $array    = shift;
	my $descr    = shift;
	my $style    = shift;
	my $isReturn = shift;
	my $ownType  = shift;

	my %attrs = (name => $name);
	
	if ($style eq $RPC_STYLE) {
		$attrs{type} = Pod::WSDL::Utils::getTypeDescr($type, $array, $ownType);
	} elsif ($style eq $DOCUMENT_STYLE) {
		$attrs{element} = ($isReturn ? lcfirst $RETURN_SUFFIX_NAME : $name) . $PART_IN . ucfirst $me->requestName
	}
	
	if ($descr and $me->writer->withDocumentation) {
		$me->writer->wrElem($START_PREFIX_NAME, 'wsdl:part', %attrs);
		$me->writer->wrDoc($descr);
		$me->writer->wrElem($END_PREFIX_NAME, 'wsdl:part');
		
	} else {
		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'wsdl:part', %attrs);
	}
}

sub writeBindingOperation {
	my $me       = shift;
	my $location = shift;
	my $use      = shift;

	$me->writer->wrElem($START_PREFIX_NAME, "wsdl:operation", name => $me->name);
	$me->writer->wrElem($EMPTY_PREFIX_NAME, "wsdlsoap:operation", soapAction => "");
	$me->writer->wrElem($START_PREFIX_NAME, "wsdl:input", name => $me->requestName);
	$me->writer->wrElem($EMPTY_PREFIX_NAME, "wsdlsoap:body", encodingStyle => "http://schemas.xmlsoap.org/soap/encoding/", namespace => $location, use => $use);
	$me->writer->wrElem($END_PREFIX_NAME, "wsdl:input");
	
	unless ($me->oneway) {
		$me->writer->wrElem($START_PREFIX_NAME, "wsdl:output", name => $me->return ? $me->responseName : $EMPTY_MESSAGE_NAME);
		$me->writer->wrElem($EMPTY_PREFIX_NAME, "wsdlsoap:body", encodingStyle => "http://schemas.xmlsoap.org/soap/encoding/", namespace => $location, use => $use);
		$me->writer->wrElem($END_PREFIX_NAME, "wsdl:output");
	}
			
	for my $fault (@{$me->faults}) {
		$me->writer->wrElem($START_PREFIX_NAME, "wsdl:fault", name => $fault->wsdlName);
		$me->writer->wrElem($EMPTY_PREFIX_NAME, "wsdlsoap:fault", name => $fault->wsdlName, encodingStyle => "http://schemas.xmlsoap.org/soap/encoding/", namespace => $location, use => $use);
		$me->writer->wrElem($END_PREFIX_NAME, "wsdl:fault");
	}

	$me->writer->wrElem($END_PREFIX_NAME, "wsdl:operation");
}

sub writeDocumentStyleSchemaElements {
	my $me    = shift;
	my $types = shift;
	
	for my $param (@{$me->params}) {
		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'element', 
			name => $param->name . $PART_IN . ucfirst $me->requestName,
			type => Pod::WSDL::Utils::getTypeDescr($param->type, $param->array, $types->{$param->type}));
	}

	for my $fault (@{$me->faults}) {
		next if $me->writer->faultMessageWritten($fault->wsdlName . $MESSAGE_PART);
		
		$me->writer->registerWrittenFaultMessage($fault->wsdlName . $MESSAGE_PART);

		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'element', 
			name => $fault->wsdlName . $MESSAGE_PART,
			type => Pod::WSDL::Utils::getTypeDescr($fault->type, 0, $types->{$fault->type}));
	}

	if (!$me->oneway and $me->return) {
		$me->writer->wrElem($EMPTY_PREFIX_NAME, 'element', 
			name => lcfirst $RETURN_SUFFIX_NAME . $PART_IN . ucfirst $me->requestName,
			type => Pod::WSDL::Utils::getTypeDescr($me->return->type, $me->return->array, $types->{$me->return->type}));
	}
}
1;
__END__

=head1 NAME

Pod::WSDL::Method - Represents a method in Pod::WSDL (internal use only)

=head1 SYNOPSIS

  use Pod::WSDL::Method;
  my $m = new Pod::WSDL::Method(name => 'mySub', writer => 'myWriter', doc => new Pod::WSDL::Doc($docStr), return => new Pod::WSDL::Return($retStr));

=head1 DESCRIPTION

This module is used internally by Pod::WSDL. It is unlikely that you have to interact directly with it. If that is the case, take a look at the code, it is rather simple.

=head1 METHODS

=head2 new

Instantiates a new Pod::WSDL::Method.

=head2 Parameters

=over 4

=item

name - name of the method, mandatory

=item

doc - a Pod::WSDL::Doc object, can be omitted, use method doc later

=item

return - a Pod::WSDL::Return object, can be omitted, use method return later

=item

params - ref to array of Pod::WSDL::Param objects, can be omitted, use addParam() later

=item

faults - ref to array of Pod::WSDL::Fault objects, can be omitted, use addFault() later

=item

oneway - if true, method is a one way operation

=item

writer - XML::Writer-Object for output, mandatory

=back

=head2 addParam

Add a Pod::WSDL::Param object to Pod::WSDL::Method

=head2 addFault

Add a Pod::WSDL::Fault object to Pod::WSDL::Method

=head2 return

Get or Set the Pod::WSDL::Return object for Pod::WSDL::Method

=head2 doc

Get or Set the Pod::WSDL::Doc object for Pod::WSDL::Method

=head2 requestName

Get name for request in XML output

=head2 responseName

Get name for response in XML output

=head2 writeBindingOperation

Write operation child for binding element in XML output

=head2 writeMessages

Write message elements in XML output

=head2 writePortTypeOperation

Write operation child for porttype element in XML output

=head1 EXTERNAL DEPENDENCIES

  [none]

=head1 EXAMPLES

see Pod::WSDL

=head1 BUGS

see Pod::WSDL

=head1 TODO

see Pod::WSDL

=head1 SEE ALSO

  Pod::WSDL :-)
 
=head1 AUTHOR

Tarek Ahmed, E<lt>bloerch -the character every email address contains- oelbsk.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Tarek Ahmed

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.5 or,
at your option, any later version of Perl 5 you may have available.

=cut