package Indyvoter::Store;

use strict;
use warnings;
use vars qw( $AUTOLOAD );

our %Schema;

sub base () {
    "Indyvoter::Object"
}

sub AUTOLOAD {
    my ($self, $class, @args) = @_;
    my $method = $AUTOLOAD;

    $method =~ s/^.*:://os;
    $self->load( $class );
    "Indyvoter::Object::$class"->$method( @args );
}

sub set_db {
    my $self = shift;
    $self->base->set_db( Main => @_ );
}

sub load {
    my ($self, $class) = @_;

    my $base   = $self->base;
    my $target = "${base}::${class}";
    return $Schema{$target} if $target->can("table"); # i.e. is loaded

    my ($schema) = "${base}::Class"->search( name => $class );
    die "Can't locate $target schema" unless $schema;
    
    my $ok = eval "require $target";
    eval "package $target; use base '$base';" unless $ok;

    $target->table( lc $target->name );

    my @properties = $schema->properties;
    my @columns;
    for my $prop (@properties) {
	my $relation = $prop->relation;
	if (not $relation) {
	    push @columns, $prop->name 
	} else {
	    $self->load( $prop->class ); # so Class::DBI doesn't try it
	    push @columns, $prop->name
		if $relation eq "has_a";
	}
    }

    $target->columns( Primary => "rec_id" );
    $target->columns( Essential => @columns ); 

    for my $prop (@properties) {
	if (my $relation = $prop->relation) {
	    $target->$relation( $prop->name, $prop->range, $prop->foreign_id );
	}
    }

    $Schema{$target} = $schema;
    return $schema;
}

1;

########################################

package Indyvoter::Object;

use base 'Class::DBI';
use Carp;

sub _croak { warn "before"; confess @_; warn "after" }

1;

########################################

package Indyvoter::Object::Class;

use base 'Indyvoter::Object';

__PACKAGE__->table( "class" );
__PACKAGE__->columns( Primary => "rec_id" );
__PACKAGE__->columns( All => qw( name title permissions reference ));
# __PACKAGE__->has_a( owner => "Indyvoter::Object::User" );
__PACKAGE__->has_a( reference => "Indyvoter::Object::Property" );
__PACKAGE__->has_many( properties => "Indyvoter::Object::Property", "domain" );

__PACKAGE__->add_trigger( before_create => \&create_table );
__PACKAGE__->add_trigger( before_delete => \&delete_table );

sub create_table {
    my ($self, %args) = @_;
    my @columns = "rec_id integer auto_increment primary key";
    push @columns, $_->store_definition for $self->properties;

    local $" = ",\n";
    my $name = $self->name;
    my $sql = "create table $name (@columns)";
    warn "create_table $sql";
    $self->db_Main->do( $sql ) or die DBI->errstr;
};

sub delete_table {
    my $self = shift;
    my $name = $self->name;
    $self->db_Main->do( "drop table $name" ) or die DBI->errstr;
}

1;

########################################
package Indyvoter::Object::Property;

use base 'Indyvoter::Object';

__PACKAGE__->table( "property" );
__PACKAGE__->columns( Primary => "rec_id" );
__PACKAGE__->columns(
    All => qw( name title store_type domain relation range foreign_id ));

# relation can be: column, has_a, has_many, might_have

__PACKAGE__->add_trigger( before_create => \&create_column );
__PACKAGE__->add_trigger( before_update => \&update_column );
__PACKAGE__->add_trigger( before_delete => \&delete_column );

sub store_definition {
    my $self = shift;
    if ($self->relation eq "has_a") {
	$self->name . " integer"
    } elsif ($self->store_type) { 
	$self->name . " " . $self->store_type;
    }
}

sub create_column {
    my $self  = shift;
    my $domain = $self->domain;

    Indyvoter::Object::Class->search( name => $domain )
	or return;

    my $type  = $self->store_definition;
    $self->db_Main->do( "alter table $domain add column $type" )
	or die DBI->errstr;
}

sub update_column {
    my $self  = shift;
    my $domain = $self->domain;
    my $old_column = $self->retrieve( $self->id )
	or return;

    my $old_name = $old_column->name;
    my $type  = $self->store_definition;
    $self->db_Main->do( "alter table $domain change column $old_name $type" )
	or die DBI->errstr;
}

sub delete_column {
    my $self  = shift;
    my $domain = $self->domain;
    my $name  = $self->name;
    $self->db_Main->do( "alter table $domain drop column $name" )
	or die DBI->errstr;
}

1;




