package Logos::Generator::Base::Property;
use strict;
use Logos::Util;

sub key {
	my $self = shift;
	my $property = shift;

	my $build = "_logos_property_key\$" . $property->group . "\$" . $property->class . "\$" . $property->name;

	return $build;
}

sub getter {
	my $self = shift;
	my $property = shift;
	my $name = shift;
	my $key = shift;
	my $type = $property->type;

	$type =~ s/\s+$//;

	if(!$name){
		# Use property name if no getter specified
		$name = $property->name;
	}

	# Build function start
	my $build = "static " . $type . " _logos_method\$" . $property->group . "\$" . $property->class . "\$" . $name . "\$" . "(" . $property->class . "* self, SEL _cmd){";

	# Build function body

	$build .= " return ";


	if ($type =~ /^(BOOL|(unsigned )?char|double|float|CGFloat|(unsigned )?int|NSInteger|unsigned|NSUInteger|(unsigned )?long (long)?|(unsigned )?short|NSRange|CGPoint|CGVector|CGSize|CGRect|CGAffineTransform|UIEdgeInsets|UIOffset|CATransform3D|CMTime(Range|Mapping)?|MKCoordinate(Span)?|SCNVector[34]|SCNMatrix4)$/){
		$build .= "[";
	}

	$build .= "objc_getAssociatedObject(self, &" . $key . ")";

	if ($type eq "BOOL"){
		$build .= " boolValue]";
	} elsif ($type eq "char"){
		$build .= " charValue]";
	} elsif ($type eq "unsigned char"){
		$build .= " unsignedCharValue]";
	} elsif ($type eq "double"){
		$build .= " doubleValue]";
	} elsif ($type =~ /^(float|CGFloat)$/){
		$build .= " floatValue]";
	} elsif ($type eq "int"){
		$build .= " intValue]";
	} elsif ($type eq "NSInteger"){
		$build .= " integerValue]";
	} elsif ($type =~ /^unsigned( int)?$/){
		$build .= " unsignedIntValue]";
	} elsif ($type eq "NSUInteger"){
		$build .= " unsignedIntegerValue]";
	} elsif ($type eq "long"){
		$build .= " longValue]";
	} elsif ($type eq "unsigned long"){
		$build .= " unsignedLongValue]";
	} elsif ($type eq "long long"){
		$build .= " longLongValue]";
	} elsif ($type eq "unsigned long long"){
		$build .= " unsignedLongLongValue]";
	} elsif ($type eq "short"){
		$build .= " shortValue]";
	} elsif ($type eq "unsigned short"){
		$build .= " unsignedShortValue]";
	} elsif ($type =~ /^(NSRange|CGPoint|CGVector|CGSize|CGRect|CGAffineTransform|UIEdgeInsets|UIOffset|CATransform3D|CMTime(Range|Mapping)?|MKCoordinate(Span)?|SCNVector[34]|SCNMatrix4)$/){
		$build .= " " . $type . "Value]";
	}

	$build .= "; }";

	return $build;
}

sub setter {
	my $self = shift;
	my $property = shift;
	my $name = shift;
	my $policy = shift;
	my $key = shift;
	my $type = $property->type;

	$type =~ s/\s+$//;

	if(!$name){
		# Capitalize first letter
		$_ = $property->name;
		$_ =~ s/^([a-z])/\u$1/;

		$name = "set" . $_;
	}

	# Remove semicolon
	$name =~ s/://;

	my $build = "static void _logos_method\$" . $property->group . "\$" . $property->class . "\$" . $name . "\$" . "(" . $property->class . "* self, SEL _cmd, " . $type . " arg){ ";


	$build .= "objc_setAssociatedObject(self, &" . $key . ", ";

	my $hasOpening = 1;

	if ($type eq "BOOL"){
		$build .= "[NSNumber numberWithBool:";
	} elsif ($type eq "char"){
		$build .= "[NSNumber numberWithChar:";
	} elsif ($type eq "unsigned char"){
		$build .= "[NSNumber numberWithUnsignedChar:";
	} elsif ($type eq "double"){
		$build .= "[NSNumber numberWithDouble:";
	} elsif ($type =~ /^(float|CGFloat)$/){
		$build .= "[NSNumber numberWithFloat:";
	} elsif ($type eq "int"){
		$build .= "[NSNumber numberWithInt:";
	} elsif ($type eq "NSInteger"){
		$build .= "[NSNumber numberWithInteger:";
	} elsif ($type =~ /^unsigned( int)?$/){
		$build .= "[NSNumber numberWithUnsignedInt:";
	} elsif ($type eq "NSUInteger"){
		$build .= "[NSNumber numberWithUnsignedInteger:";
	} elsif ($type eq "long"){
		$build .= "[NSNumber numberWithLong:";
	} elsif ($type eq "unsigned long"){
		$build .= "[NSNumber numberWithUnsignedLong:";
	} elsif ($type eq "long long"){
		$build .= "[NSNumber numberWithLongLong:";
	} elsif ($type eq "unsigned long long"){
		$build .= "[NSNumber numberWithUnsignedLongLong:";
	} elsif ($type eq "short"){
		$build .= "[NSNumber numberWithShort:";
	} elsif ($type eq "unsigned short"){
		$build .= "[NSNumber numberWithUnsignedShort:";
	} elsif ($type =~ /^(NSRange|CGPoint|CGVector|CGSize|CGRect|CGAffineTransform|UIEdgeInsets|UIOffset|CATransform3D|CMTime(Range|Mapping)?|MKCoordinate(Span)?|SCNVector[34]|SCNMatrix4)$/){
		$build .= "[NSValue valueWith " . $type . ":";
	} else {
		$hasOpening = 0;
	}

	$build .= "arg";

	if ($hasOpening){
		$build .= "]";
	}

	$build .= ", ".$policy.")";

	$build .= "; }";

	return $build;
}

sub getters_setters {
	my $self = shift;
	my $property = shift;

	my ($assign, $retain, $nonatomic, $copy, $getter, $setter);


	for(my $i = 0; $i < $property->numattr; $i++){

		my $attr = $property->attributes->[$i];

		if($attr =~ /assign/){
			$assign = 1;
		}elsif($attr =~ /retain/){
			$retain = 1;
		}elsif($attr =~ /nonatomic/){
			$nonatomic = 1;
		}elsif($attr =~ /copy/){
			$copy = 1;
		}elsif($attr =~ /getter=(\w+)/){
			$getter = $1;
		}elsif($attr =~ /setter=(\w+:)/){
			$setter = $1;
		}
	}

	my $policy = "OBJC_ASSOCIATION_";

	if($retain){
		$policy .= "RETAIN";
	}elsif($copy){
		$policy .= "COPY";
	}elsif($assign){
		$policy .= "ASSIGN";
	}else{
		print "error: no 'assign', 'retain', or 'copy' attribu...wait, how did you manage to get here?\n";
	}

	if($nonatomic){
		# The 'assign' attribute appears to be nonatomic by default.
		if(!$assign){
			$policy .= "_NONATOMIC";
		}
	}

	$property->associationPolicy($policy);

	my $build;

	my $key = $self->key($property);
	my $getter_func = $self->getter($property, $getter, $key);
	my $setter_func = $self->setter($property, $setter, $policy, $key);

	$build .= $build . "static char " . $key . ";";
	$build .= $getter_func;
	$build .= $setter_func;


	return $build;
}

sub initializers {
	my $self = shift;
	my $property = shift;

	my ($getter, $setter);

	for(my $i = 0; $i < $property->numattr; $i++){ # This could be more efficient
		my $attr = $property->attributes->[$i];

		if($attr =~ /getter=(\w+)/){
			$getter = $1;
		}elsif($attr =~ /setter=(\w+:)/){
			$setter = $1;
			$setter =~ s/://;
		}
	}

	if(!$setter){
		# Capitalize first letter
		$_ = $property->name;
		$_ =~ s/^([a-z])/\u$1/;

		$setter = "set" . $_;
	}

	if(!$getter){
		# Use property name if no getter specified
		$getter = $property->name;
	}


	my $build = "";

	$build .= "{ ";

	# Getter
	$build .= "class_addMethod(";

	$build .= "_logos_class\$" . $property->group . "\$" . $property->class . ", ";
	$build .= "\@selector(" . $getter . "), " . "(IMP)&" . "_logos_method\$" . $property->group . "\$" . $property->class . "\$" . $getter . "\$, [[NSString stringWithFormat:\@\"%s\@:\", \@encode(".$property->type.")] UTF8String]);";

	# Setter
	$build .= "class_addMethod(";
	$build .= "_logos_class\$" . $property->group . "\$" . $property->class . ", ";

	$build .= "\@selector(" . $setter . ":), " . "(IMP)&" . "_logos_method\$" . $property->group . "\$" . $property->class . "\$" . $setter . "\$, [[NSString stringWithFormat:\@\"v\@:%s\", \@encode(".$property->type.")] UTF8String]);";

	$build .= "} ";

	return $build;
}

1;
