package Yaala::Data::Setup;

use strict;
use warnings;
use vars qw#$USED_FIELDS $USED_AGGREGATIONS $SELECTS %DATAFIELDS#;

=head1 Yaala::Data::Setup

This module is currently under construction.

=cut

use Exporter;
use Carp qw#carp cluck croak confess#;
use Yaala::Config qw#get_config#;
use Yaala::Data::Persistent qw#init#;

import Yaala::Parser qw#%DATAFIELDS#;

@Yaala::Data::Setup::ISA = ('Exporter');
@Yaala::Data::Setup::EXPORT_OK = qw#$USED_FIELDS $USED_AGGREGATIONS $SELECTS %DATAFIELDS#;
import Yaala::Parser qw#%DATAFIELDS#;

$USED_FIELDS = init ('$USED_FIELDS', 'hash');
$USED_AGGREGATIONS = init ('$USED_AGGREGATIONS', 'hash');
$SELECTS = init ('$SELECTS', 'array');

my $VERSION = '$Id: Setup.pm,v 1.14 2003/12/07 14:52:22 octo Exp $';
print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);

if ($::DEBUG & 0x20)
{
	require Data::Dumper;
	import Data::Dumper qw#Dumper#;
}

read_config ();

return (1);

=head1 Routines

=head2 Yaala::Data::Setup::read_config

Parses the select-statements in the config file and returns configuration
data. To be called by Yaala::Data::Core.

=cut
sub read_config
{
	print STDERR $/, __FILE__, ': ',
	Data::Dumper->Dump ([\%DATAFIELDS], ['DATAFIELDS']) if ($::DEBUG & 0x20);
	
	unless (get_config ('select'))
	{
		print STDERR $/, __FILE__, ": Please edit the config file first!\n";
		exit (1);
	}

	for (get_config ('select'))
	{
		print STDERR $/, __FILE__, ": Select statement from config file: '$_'" if ($::DEBUG & 0x20);
		my $select = parse_select ($_);

		next unless (defined ($select));
		
		push (@$SELECTS, $select);

		$USED_AGGREGATIONS->{$_}++ for (@{$select->[0]});
		$USED_FIELDS->{$_}++ for (@{$select->[1]});
		$USED_FIELDS->{$_->[0]}++ for (@{$select->[2]});

		print STDERR $/, __FILE__, ': New selection: ',
		Data::Dumper->Dump ([$select], ['select']) if ($::DEBUG & 0x20);
	}

	if (!scalar (@$SELECTS))
	{
		print STDERR $/, __FILE__, ": No valid select-statements found. Exiting.\n";
		exit (1);
	}
}

# select: agg   from fld1 [, fld2] [where fld3   = "value"    ];
# select: bytes from date [, time] [where client = "leeloo.ff"];
sub parse_select
{
	my $line = shift;
	my $retval;

	$line =~ s/\s\s+/ /g;
	
	if (grep { $line eq $_->[3] } (@$SELECTS))
	{
		print STDERR $/, __FILE__, ": Found duplicated selection ``$line''.",
		$/, __FILE__, ": This is probably coming from Yaala::Data::Persistent and is nothing to worry about."
		if ($::DEBUG);

		return (undef);
	}
	
	#if ($line =~ m/^(\w+) BY (\w+(?:,\s?\w+)*)(?: WHERE (.+))?$/i)
	if ($line =~ m/^(\w+(?:\s*,\s*\w+)*)\s+BY\s+(\w+(?:\s*,\s*\w+)*)(?:\s+WHERE\s+(.+))?$/i)
	{
		my ($agg_exp, $fld_exp, $where_exp) = ($1, $2, $3);

		my @aggs = ();
		for (split (m/\s*,\s*/, $agg_exp))
		{
			my $agg = lc ($_);

			if (!defined ($DATAFIELDS{$agg}))
			{
				print STDERR $/, __FILE__, ": Aggregation ``$agg'' not provided by parser. ",
				"Ignoring this aggregation.";
				next;
			}
			elsif ($DATAFIELDS{$agg} !~ m/^agg/i)
			{
				print STDERR $/, __FILE__, ": ``$agg'' is not an aggregation. Ignoring it.";
				next;
			}

			push (@aggs, $agg);
		}
		if (!scalar (@aggs))
		{
			print STDERR $/, __FILE__, ": No valid aggregation found. Ignoring this select-statement.";
			return (undef);
		}

		my @fields = ();
		for (split (m/\s*,\s*/, $fld_exp))
		{
			my $fld = lc ($_);
			
			if (!defined ($DATAFIELDS{$fld}))
			{
				print STDERR $/, __FILE__, ": Field '$fld' not provided by parser. Ignoring it.";
				next;
			}

			push (@fields, $fld);
		}
		if (!scalar (@fields))
		{
			print STDERR $/, __FILE__, ": No valid fields found. Ignoring this select-statement.";
			return (undef);
		}

		my @wheres = parse_where ($where_exp);
		
		$retval = [\@aggs, \@fields, \@wheres, $line];
	}
	else
	{
		print STDERR $/, __FILE__, ": Unable to parse select statement:",
		$/, __FILE__, ":     $line",
		$/, __FILE__, ": Ignoring it.";
	}
	
	return ($retval);
}

# where ...
# key = "val"
# key =~ "regex"
# key < val
# key > val
# key == val
sub parse_where
{
	my $where_exp = shift;
	my @where = ();

	if (!defined ($where_exp))
	{
		return (@where);
	}
	
	for (split (m/\s?,\s?/, $where_exp))
	{
		my $exp = $_;
		if ($exp =~ m/(\w+)\s?([<>=~!]+)\s?(.+)/)
		{
			my ($fld, $op, $val) = ($1, $2, $3);
			if (!defined ($DATAFIELDS{$fld}))
			{
				print STDERR $/, __FILE__, ": Error in where-clause: Field '$fld' ",
				"is unknown. Ignoring it.";
				next;
			}

			my $type = '';
			if ($DATAFIELDS{$fld} =~ m/:/)
			{
				$type = (split (m/:/, $DATAFIELDS{$fld}))[1];
			}

			unless ($op =~ m/^[<>=!]=$/
					or $op eq '=~'
					or $op eq '!~'
					or $op eq '<' or $op eq '>')
			{
				print STDERR $/, __FILE__, ": Error in where-clause: Operator '$op' ",
				"is unknown. Ignoring it.";
				next;
			}

			$val =~ s/^['"]|['"]$//g;

			if ($type ne 'numeric')
			{
				$op = 'eq' if ($op eq '==');
				$op = 'ne' if ($op eq '!=');
				$op = 'gt' if ($op eq '>');
				$op = 'ge' if ($op eq '>=');
				$op = 'lt' if ($op eq '<');
				$op = 'le' if ($op eq '<=');
			}
			elsif ($type eq 'numeric' and
				($op eq '=~' or $op eq '!~'))
			{
				print STDERR $/, __FILE__, ": Error in where clause: Can't use regex ",
				"with numeric field $fld. Ignoring this clause.";
				next;
			}

			print STDERR $/, __FILE__, ":     New where-statement: [$fld, $op, $val]" if ($::DEBUG & 0x20);

			push (@where, [$fld, $op, $val]);
		}
		else
		{
			print STDERR $/, __FILE__, ": Error in where-clause: Unable to parse '$exp'. ",
			"Ignoring it.";
		}
	}

	return (@where);
}
