package Obj;

use strict;
use warnings;
use include;

# Creates a new template from another. Makes a template.
sub newclass {
  my $base = shift;
  my $name = shift;
  my $class = $base->new(@_, Instance => 0, Class => $name);
  die "Already a $name class!\n" if GAME->{Classes}{$name};
  GAME->{Classes}{$name} = $class;
  return $class;
}

# Creates a new object from another. Makes an instance.
sub new {
  my $base = shift;

  my $self = bless {
    Instance => 1,
    @_
  }, "Obj";

  unless ($base eq "Obj") {
    # Normal template based off another template. We're not making a root
    # template or anything.
    # If we're subclassing an instance, something might be up.
    die "Cloning an instance... why?\n" if $base->{Instance};
    $self->{Base} = $base;
  }
  # Yank in all of its listed actions and methods...
  my $package;
  if ($self->{Instance}) {
    # We were called directly.
    $package = (caller)[0];
  } else {
    # newclass called us, so see who called us 2 callstack thingies ago.
    $package = (caller(1))[0];
  }
  no strict "refs";
  foreach my $type ("Actions", "Methods") {
    next unless $self->{$type};
    my @acts = @{ $self->{$type} };
    $self->{$type} = {};
    foreach my $code (@acts) {
      if ($code =~ s/^://) {
        # We're a stub/fake action, meaning this character can do this
        # action, but the Action stage of this action doesn't exist... an
        # earlier stage redirects.
        $self->{$type}{$code} = "STUB";
      } else {
        $self->{$type}{$code} = \&{ "${package}::$code" };
      }
      next unless $type eq "Actions";
      # Now pull in all other stages of the action that exist and set up
      # reactions for them.
      foreach my $stage (@{ $self->g("Stages") }) {
        next unless my $react = $package->can($stage . "_$code");
        $self->react("DEFAULT" => $stage, $code,
          by => $react
        );
      }
    }
  }
  use strict "refs";

  # TODO: Weaken ref? Garbage collection? Do old objects get shifted up and
  # junk?
  push @{ GAME->{Objs} }, $self;
  $self->{ID} = $#{ GAME->{Objs} };
  # Go through all the existing init() routines if they exist. Generic to
  # specific!
  foreach ($self->bases) {
    $_->{init}->($self) if $_->{init};
  }
  return $self;
}

# Returns the object's list of bases from general to specific.
sub bases {
  my $self = shift;
  my @ls;
  push @ls, $self unless $self->{Instance};
  my $cur = $self;
  while (1) {
    $cur = $cur->{Base};
    last unless $cur;
    unshift @ls, $cur;
  }
  return @ls;
}

our $AUTOLOAD;

# Since this is a classless object system, we have to find methods ourselves.
sub AUTOLOAD {
  my $self = shift;
  my $do = $AUTOLOAD;
  $do =~ s/^.*:://;
  return if $do eq "DESTROY";
  if ($do =~ m/_/) {
    my ($time, $act) = split("_", $do);
    $self->{actdat} = { args => [@_] };
    my $signal = $self->signal($act, $time, );
    delete $self->{actdat};
    return ($signal eq "none") ? undef : $signal;
  }
  my $action = $self->g("Actions", $do);
  my $method = $self->g("Methods", $do);
  if ($action and $method) {
    die "$method is an action and method! Eek!\n";
  } elsif ($action) {
    return $self->do_act($do, @_);
  } elsif ($method) {
    return $method->($self, @_);
  } else {
    die "This object can't do $do.\n";
  }
}

# We find the "most specific" data. We should only be called from accessors. No
# abusing me, OK?
sub g {
  my ($self, @keys) = @_;
  my $base = $self;
  my $var;
  my $sofar = -1;
  while (1) { # For each parent of $self, starting with $self itself...
    $var = $base;
    my $sofar = "";
    my $start = 1;
    foreach (@keys) {
      # For the very first key, just GET it normally.
      if ($start) {
        $start = 0;
        $var = $var->{$_};
        $sofar++;
        last unless defined $var;
        next;
      }

      my $ref = ref $var;
      if ($ref eq "Obj") {
        # Delegate the call now. This makes sense. Don't make me explain it.
        $var = $var->g(@keys[$sofar .. $#keys]);
        last;
      } elsif ($ref eq "HASH" and defined $var->{$_}) {
        $var = $var->{$_};
      } elsif ($ref eq "ARRAY" and defined $var->[$_]) {
        $var = $var->[$_];
      } else {
        $var = undef;
        last;
      }
      $sofar++;
    }
    last if defined $var;
    last unless $base->{Base};
    $base = $base->{Base};
  }
  return $var if defined $var;
  # TODO: we may want to avoid an undef error for some reason...
  return undef;
}

# Go through and do an entire action.
sub do_act {
  my ($self, $action, @args) = @_;
  die "can't do $action!\n" unless $self->g("Actions", $action);
  my $act = { Args => [@args] };
  # Turn flags into... flags.
  while ($args[0] and $args[0] =~ s/^\-//) {
    $act->{ shift(@args) } = 1;
    shift @{ $act->{Args} };
  }
  # Go through each of the stages of an Action and send the appropriate
  # signal. At any point, a reaction could halt the flow.
  my $return;
  foreach my $time (@{ $self->g("Stages") }) {
    if ($time eq "-") { # ACTION!
      my $do = $self->g("Actions", $action);
      die "Nothing redirected before the $action STUB!\n" if $do eq "STUB";
      $return = $do->($self, $act);
    } else {
      $return = $self->signal($action, $time, $act);
    }
    # Reactions can pretty much continue or stop the flow. Any information
    # should be recorded in $params. Or alternatively, a reaction could
    # completely change the flow and prompt a new action. How inconvenient!
    if (ref $return eq "ARRAY") {
      return $self->do_act(@$return);
    }
    if ($return and $return eq STOP) {
      $return = 0; # so "blah if $self->act()" works
      last;
    }
  }
  return $return; # if something needs $act, an After or whatever can return it
}

# Send a signal, query the event DB, and execute any appropriate reactions in
# the right order.
sub signal {
  my ($self, $action, $time, $act) = @_;
  # We sound so fancy! "Query the DB and send a signal" - HAH! All we do is
  # loop.
  my (@hi, @lo);  # Reactions with inhibitions get first pick
  foreach (values %{ GAME->{Events}{$action}{$time} }) {
    $_->{Inhibit} ? push(@hi, $_) : push(@lo, $_);
  }

  # TODO: If two (or more) reactions conflict (inhibit each other, either
  # directly or conditionally in the code), then that's a major problem since
  # their execution order is pretty much arbitrary. In general, if two or more
  # reactions for the same signal influence each other's conditions somehow,
  # then that's kinda bad since we don't even test for it.

  # Go get em.
  my $return = "none";  # if there are no reactions, keep going
  $act->{Inhibit} = {};
  foreach my $react (@hi, @lo) {
    my $name = $react->{Name};
    next if $act->{Inhibit}{$name};

    # Check the standard context requirements. Weird ones can be done in the
    # reaction itself.
    # If the requirements fail, then keep on going with the flow... just don't
    # do the reaction!

    if (my $mustbe = $react->{Actor}) {
      # If the reaction says the actor of this event must be a type of object,
      # test for that. But it might also say it has to be a SPECIFIC object.
      # Test for that too.
      if ($mustbe->{Instance}) {
        next unless $self->same($mustbe);
      } else {
        next unless $self->isa($mustbe->{Class});
      }
    }

    $return = $react->{Reaction}->($self, $act);
    $return ||= 1;    # undefined (blank return) same as blank. but undefined
                      # warnings are annoying.
    return $return if ref $return eq "ARRAY";   # action redirection
    $self->ignore($action, $time, $react->{Name}) if $return eq "SUICIDE";
    last unless $return eq STOP;
  }
  return $return;
}

# We permanently remove a reaction from the event DB.
sub ignore {
  my ($self, $action, $time, $name) = @_;
  delete GAME->{Events}{$action}{$time}{$name};
}

# Install a new reaction
sub react {
  my ($self, $name, $time, $action, %dat) = @_;
  # Autovivification my ass.
  my $ls = GAME->{Events};
  $ls->{$action} ||= {};
  $ls = $ls->{$action};
  $ls->{$time} ||= {};
  $ls = $ls->{$time};

  die "Already a reaction for $time $action called $name!" if $ls->{$name};
  $ls->{$name} = {
    Name       => $name,
    Reaction   => $dat{by},
    Actor      => $self,
    Inhibit    => {
      DEFAULT  => 1
    }
    # TODO: handle other stuff in %dat
  };
  delete $ls->{$name}{Inhibit} if $name eq "DEFAULT";
  return 1;
}

# This checks to see if the specified class/template is somewhere in the
# object's inheritance line.
sub isa {
  my ($self, $class) = @_;
  foreach ($self->bases) {
    return 1 if $class eq $_->{Class};
  }
  return;
}

# Determines if the two objects are exactly the same.
sub same {
  my ($self, $other) = @_;
  return unless ref $other eq "Obj";
  return 1 if $self->{ID} == $other->{ID};
}

# TODO: a msg thingy?
# TODO: actual real event stuff. and decorators. and effects.
# TODO: serialize stuff. and load stuff.
# TODO: a thing that makes the std accessors. aka the g() ones and the instance
# ones.

42;
