/usr/local/CPAN/Dockhand/Dockhand/Types.pm


package Dockhand::Types;
use strict;
use warnings;

use Exporter;

our @ISA    = qw(Exporter);
our @EXPORT = qw(VARCHAR INT DEC NUM CHAR UNDEF NUMCHAR
				 get_col_type get_num_col_type get_longest_item
				 is_in get_col_length get_item_length get_item_type
				 make_format_type);

use lib $ENV{'DOCKHAND_PATH'} . '/lib';
#use Dockhand::File::CSV;

# FIXME: Findout why this makes it run slow
# TODO: Make this read table from config file
BEGIN {
	# Dynamically create all is_[type] functions
	my $notchar = '/^\-\d+/';
	my $char    = '/\>|\<|\$|\,|[a-zA-Z]|\-/i' . "and !$notchar";
	my $int     = '/-?(\d+)/';
	my $dec     = '/-?(\d+\.\d+)/';
	my $num     = "$int or $dec";
	my $undef   = '/UNDEF/i';
	my $numchar = "!$notchar and $char and ( $int or $dec )";
	
	my %PATTERNS = (
		CHAR    => "$char   or /CHAR/i",
		INT     => "$int    or /INT/i",
		DEC     => "$dec    or /DEC/i",
		NUM     => "$num    or /NUM/i or /INT/i or /DEC/i",
		UNDEF   => $undef,
		NUMCHAR => "\( $numchar \) or /NUMCHAR/i"
	);

	for my $type (keys %PATTERNS) {
		no strict 'refs';
		*{'is_' . lc $type} = sub { 
			local $_ = shift;
			return 1 if eval $PATTERNS{$type};
			return 0;
		}; 
	}
}

use constant { VARCHAR => 'VARCHAR2', 
			   INT     => 'INTEGER EXTERNAL',
			   DEC	   => 'DECIMAL EXTERNAL', 
			   NUM     => 'NUMBER',
			   CHAR    => 'CHAR',
			   UNDEF   => 'UNDEF',
			   NUMCHAR => 'NUMCHAR' };

# FIXME: This is a mess
sub get_col_type {
	my $col       = shift;
	my $filetype  = shift;
	my $fieldname = shift if ($filetype =~ /CTL/i);

	my $num_types = get_num_col_types($col, $filetype);
	my @types     = keys %$num_types;

	# Dominant Type (numericaly)
	my @dom_type = sort { $num_types->{$b} <=> $num_types->{$a} } keys %$num_types;

	if ( $dom_type[0] =~ /UNDEF/i ) {
		unless ($dom_type[1]) {
			return CHAR if $filetype =~ /CTL/i;

			my $len = get_col_length($col);
			return VARCHAR . "\($len\)" if $filetype =~ /SQL/i;
		}
		shift @dom_type;
	}
	if ( $dom_type[0] =~ /NUMCHAR/i ) {
		if ( $dom_type[1] and ($dom_type[1] =~ /CHAR/i) ) {
			if ( $filetype =~ /SQL/i ) {
				my $len = get_col_length($col);
				return VARCHAR . "\($len\)";

			} else { return CHAR }
		}
		return NUM if $filetype =~ /SQL/i;

		my $longest = get_longest_item($col);
		my $format_str = make_format_string($longest);

		return 'CHAR "TO_NUMBER(:' .
			$fieldname . "\,'". $format_str .'\')"';
	} elsif ( $dom_type[0] =~ /INT|DEC|NUMBER/i ) {
		if ( is_in('/NUMCHAR/i', @dom_type) && ($filetype =~ /CTL/i) ) { 
			my $longest = get_longest_item($col);
			my $format_str = make_format_string($longest);
			return 'CHAR "TO_NUMBER(:' .
				$fieldname . "\,'". $format_str .'\')"';
				
		} elsif ( is_in('/NUMCHAR/i', @dom_type) && ($filetype =~ /SQL/i) ) { 
			return NUM;
			
		} elsif ( is_in('/CHAR/i', @dom_type) && ($filetype =~ /CTL/i) ) { 
			return CHAR;
			
		} elsif ( is_in('/CHAR/i', @dom_type) && ($filetype =~ /SQL/i) ) {
		   my $len = get_col_length($col);
		   return VARCHAR . "\($len\)";
		   
		} else { return $dom_type[0] }
	} elsif ( $dom_type[0] =~ /VARCHAR|CHAR/i ) {
		if ( $filetype =~ /SQL/i ) {
			my $len = get_col_length($col);
			return VARCHAR . "\($len\)";
		} else { return CHAR }
	} else { return $dom_type[0] }
}

sub get_num_col_types {
	my $col      = shift;
	my $filetype = shift;
	my (@types, %num_types);
	foreach my $item (@$col) {
		my $type = get_item_type($item, $filetype); 
		push @types, $type;
		unless (is_in('/'.$type.'/i',@types)) {
			$num_types{$type} = 1;
			next;
		}
		++$num_types{$type}
	}
	return \%num_types;
}

sub get_longest_item {
	my $col = shift;
	my %lengths;
	foreach (@$col) { $lengths{$_} = get_item_length($_) }
	my @longest = sort { $lengths{$b} <=> $lengths{$a} } keys %lengths;
	return $longest[0];
}

sub is_in {
	my $query = shift;
	foreach (@_) { return 1 if (eval $query) }
	return 0;
}

sub get_col_length {
	my $col = shift;
	my @lengths;
	foreach (@$col) { push @lengths, get_item_length($_) }
	@lengths = sort {$a <=> $b} @lengths;
	my $len  = $lengths[(int(@lengths)-1)];
	return 3 unless $len;
	return $len;
}

sub get_item_length {
	local $_ = shift;
	use bytes; return length;
}

# FIXME: This can be simplified
# maybe even completely dynamic
sub get_item_type {
	my $item     = shift;
	my $filetype = shift; # SQL or CTL(Control)

	if (($filetype =~ /SQL/i) || !$filetype) {
		if    (is_numchar($item)) { return NUMCHAR }
		elsif (is_char($item))    { return VARCHAR } 
		elsif (is_num($item))     { return NUM } 
		else				      { return UNDEF }
	} elsif ($filetype =~ /CTL/i) {
		if    (is_numchar($item)) { return NUMCHAR } 
		elsif (is_char($item))    { return CHAR } 
		elsif (is_dec($item))     { return DEC } 
		elsif (is_int($item))     { return INT } 
		else				      { return UNDEF }
	} else { return UNDEF }
}

sub make_format_string {
	local $_ = shift;
	s/\d/9/g; 
	s/\-|\+/S/g;
	return $_;
}
1;