use strict;
use warnings;

package CamelBones;

use CamelBones::Foundation;
use CamelBones::Foundation::Constants;
use CamelBones::AppKit;
use CamelBones::AppKit::Constants;

use CamelBones::NSPoint;
use CamelBones::NSRange;
use CamelBones::NSRect;
use CamelBones::NSSize;

use CamelBones::TiedArray;
use CamelBones::TiedDictionary;

CamelBones::Foundation::Globals->import;
CamelBones::AppKit::Globals->import;

require Exporter;
our @ISA = qw(Exporter);
our $VERSION = '0.2.2';
our @EXPORT = ();
our @EXPORT_OK = (	'NSLog', 'NSClassFromString',
					@CamelBones::Foundation::EXPORT,
					@CamelBones::Foundation::Constants::EXPORT,
					@CamelBones::Foundation::Globals::EXPORT,
                    @CamelBones::AppKit::EXPORT,
                    @CamelBones::AppKit::Constants::EXPORT,
                    @CamelBones::AppKit::Globals::EXPORT,
                );
our %EXPORT_TAGS = (
    'All'		=> [@EXPORT_OK],
    'Foundation' => [
    		'NSLog', 'NSClassFromString',
    		@CamelBones::Foundation::EXPORT,
    		@CamelBones::Foundation::Constants::EXPORT,
    		@CamelBones::Foundation::Globals::EXPORT,
    	],
    'AppKit' => [
    		@CamelBones::AppKit::EXPORT,
    		@CamelBones::AppKit::Constants::EXPORT,
    		@CamelBones::AppKit::Globals::EXPORT,
    	],
);

# Defaults for options

# Outlet typing options
our $StrictTypeChecking = 1;
our $VeryStrictTypeChecking = 1;

# Warnings
our $ShowUnhandledTypeWarnings = 0;

# Conversion options
our $ReturnStringsAsObjects = 0;

&CamelBones::boot_CamelBones();

# NSObject defines autoloading behavior for all classes
package NSObject;
@NSObject::ISA = qw(Exporter);
@NSObject::EXPORT = qw();
$NSObject::VERSION = '0.2.2';

use overload
	'==' => \&CB_EQUALITY,
	'eq' => \&CB_EQUALITY,
	'bool' => \&CB_BOOL;

sub CB_EQUALITY {
	my ($a, $b) = @_;
	return 0 unless (defined $a && defined $b);
	
	return ($a->{'NATIVE_OBJ'} == $b->{'NATIVE_OBJ'});
}

sub CB_BOOL {
	return (defined($_[0]) && defined($_[0]->{'NATIVE_OBJ'}));
}

sub UNIVERSAL::outlet {
  my $outlet = shift; # name of outlet
  my %param = @_;
  my $type = $param{isa} || 'NSObject'; # enforce type checking?
  my $package = (caller)[0]; # the package the caller is in


  {
    no strict 'refs';

    # Simple setup of hash.
    $package::OBJC_OUTLET{$outlet} = $type;


    # Or we can make methods

    # setter
    my $setter = 'set'.ucfirst($outlet);
    unless ( UNIVERSAL::can($package, $setter) ) { # don't overwrite existing method
      *{ $package.'::'.$setter } = sub {
        my ($self, $set) = @_;
        if ($CamelBones::StrictTypeChecking != 0) {
			unless (UNIVERSAL::isa($set, $type)) {
			  NSLog("Can't set $outlet to $set - object must be a $type");
			  if ($CamelBones::VeryStrictTypeChecking != 0) {
				return;
			  }
			}
		}
        $self->{$outlet} = $set;
      };
      
      # Export it to objc
      ${$package.'::OBJC_EXPORT'}{$setter . ':'}={'args'=>'@', 'return'=>'v'};
    }
  
    # getter
    my $getter = lcfirst($outlet);
    unless ( UNIVERSAL::can($package, $getter) ) {
      *{ $package.'::'.$getter } = sub {
        my ($self) = @_;
        $self->{$outlet};
      };

      # Export it to objc
      ${$package.'::OBJC_EXPORT'}{$getter}={'args'=>'', 'return'=>'@'};
    }
  }
}

sub UNIVERSAL::MODIFY_CODE_ATTRIBUTES {
	my ($class, $sub, @attrs) = @_;
	my $selector = '';
	my $props = {};
	my @unknown = ();

	# Iterate over each attribute, handling the known ones
	foreach (@attrs) {
		if (/Selector\((.*)\)/) {
			$selector = $1;
		} elsif ($_ eq 'IBAction') {
			$props->{'args'} = '@';
			$props->{'return'} = 'v';
		} elsif (/ArgTypes\((.*)\)/) {
			$props->{'args'} = $1;
		} elsif (/ReturnType\((.*)\)/) {
			$props->{'return'} = $1;
		} else {
			push(@unknown, $_);
		}
	}

	# If a selector and one or more attributes were found, export them
	if ($selector ne '' && scalar(keys %$props) != 0) {
		no strict 'refs';
		${$class.'::OBJC_EXPORT'}{$selector} = $props;

		my $method = $selector;
		$method =~ s/:/__/g;
		${$class.'::OBJC_EXPORT'}{$selector}{'method'} = $method;
		*{$class.'::'.$method} = $sub;

	} else {
		CamelBones::NSLog("Warning - Method not exported attributes used without Selector");
	}

	return @unknown;
}

sub AUTOLOAD {
	no strict;
	no warnings;

    my $subName = $NSObject::AUTOLOAD;
    my $selString = $subName;
    my @args = undef;
    my $native = undef;
    my $class = undef;
    my $returnObject = undef;

    $selString =~ s/^\w+:://;
    $selString =~ s/_/:/g;
    if (@_ > 1 && $selString !~ /:$/) {
        $selString .= ':';
    }
    if (@_ && ref $_[0]) {
        my $self = shift;
        $native = $self->{NATIVE_OBJ};
        @args = @_;
        $returnObject = &CamelBones::_callObjectMethod($native, $selString, \@args);
    } else {
        $class = shift;
        @args = @_;
        $returnObject = &CamelBones::_callClassMethod($class, $selString, \@args);
    }

	if (wantarray && ref($returnObject)) {
		if ($returnObject->isa('NSArray')) {
			my @array;
			tie(@array, 'CamelBones::TiedArray', $returnObject);
			return(@array);
		} elsif ($returnObject->isa('NSDictionary')) {
			my %hash;
			tie(%hash, 'CamelBones::TiedDictionary', $returnObject);
			return (%hash);
		} else {
			return $returnObject;
		}
	} else {
		return $returnObject;
	}
}

# Happy Perl
1;
