#!/usr/bin/perl

# Copyright (c) 2004 Alan W. Mahoney. All rights reserved. 
# This program is free software.  You may modify and/or 
# distribute it under the same terms as Perl itself.  
# This copyright notice must remain attached to the file.  

package Tie::Tee;
sub TIEHANDLE {
  my $class = shift;
  my @handles;
  my $ofh = select();
  for my $path (@_) {
    open(my $fh, ">$path") || die "can't write $path";
    select($fh);
    $|++;
    push @handles, $fh;
  }
  select($ofh);
  bless \@handles,$class;
}

sub PRINT {
  my $self = shift;
  my $ok = 0;
  for my $fh (@$self) {
    $ok += print $fh @_;
  }
  return $ok==@$self;
}

# package AsymmetricTransaction;
# includes stuff asked for
# for my $field (qw( 
# 		   offeree
# 		   offerer
# 		   offer_time
# 		   status
# 		   meat
# 		   )) {
#   no strict "refs";
#   *$field = sub {
#     my $self = shift;
#     $self->{$field} = shift if @_;
#     return $self->{$field};
#   }
# }

# sub new {
#   my $invocant = shift;
#   my $class = ref($invocant) || $invocant;
#   my $item = { meat => 0,
# 	       offeree => 0,
# 	       time_offered => 0,
# 	       @_ };
#   bless $item, $class;
# }

package main;

my $ID = '$Id: invbot.pl 84 2005-01-20 17:10:09Z alan $';
__FILE__ =~ m|(.*/)(.*)|;
my $FILE = $2;
my $VERSION = 0.60;

my $ITEM_LIMIT = 2_000_000_000;
my $MEAT_LIMIT = 2_000_000_000;

use warnings;
use strict;

use KoLBot;

use KoLBot::Items;
use KoLBot::Recipe;
use KoLBot::Recipes;
use KoLBot::KMail;
use KoLBot::Limiter;
use KoLBot::FileStateManager;
use KoLBot::DBStateManager;

use POSIX qw ( floor );

###################################################################
# Read configuration
###################################################################
my $conf_file;
(@ARGV == 0) and $conf_file = 'chefbot.conf';
(@ARGV == 1) and $conf_file = $ARGV[0];
(@ARGV >  1) and 
  die "$FILE requires a configuration file.\n\n" 
  . "$FILE chef.conf\n";

our ($config,$bot_conf,$helpscript);
# by default, it is in testing mode, and doesn't respond to anyone. Heh.
our $testing = 1;
our $test_users = "";

# Here we could do all kinds of validation and jail stuff, but we don't.
# Let people shoot themselves in the foot with config file if they want.
# Should check at least that file exists, but it'll prolly die if not.
do $conf_file or die;

# We should have a log file, start logging
if($config->{logfile}) {
  tie *STDOUT, "Tie::Tee", ('-','>'.$config->{logfile});
  tie *STDERR, "Tie::Tee", ('-','>'.$config->{logfile});
}

push @{$config->{ignore_list}},(142302, # chatbot
				146057, # warehouse23
				 48821, # silversurfer
				 95114, # lawrence welk
				 86287, # muzzy
				);

$testing and warn "BOT IN TESTING MODE\n";

defined($bot_conf) or
  die "you have to define at least one account to log on.";

$bot_conf->[0]->{process_message} = \&process_message;

###################################################################
# Static, read only stuff: file/fixed
###################################################################

#
# Items/recipes
#

# Items, read-only, need one database for all bots.
my $vitems = new KoLBot::Items(config => { file => 'items.txt'});
$vitems->init();
my $recipes = new KoLBot::Recipes(config => { file => 'recipes.txt'});
$recipes->init;

my $myname = $bot_conf->[0]->{user};
$helpscript =~ s/\$VERSION/$VERSION/g;
$helpscript =~ s/\$NAME/$myname/g;
$helpscript =~ s/\$FILE/$FILE/g;

###################################################################
# Database connection
###################################################################
###################################################################
# StateManagers
###################################################################

my ($dbh,$sm);

if($config->{smtype} eq 'db') {
  use DBI;

  $config->{db_conf}->{dbms} eq 'mysql4' 
    or die "escrowbot currently supports only mysql4";

  $sm  = new KoLBot::DBStateManager(
			    table_name => $config->{db_conf}->{table_pre}
				  . 'managed_state',);
} else {
  $sm = new KoLBot::FileStateManager(config => 
				   { path => $config->{statedir} } );
}

sub dbconnect {
  if($config->{smtype} eq 'db') {
    my $db = 'dbi:mysql:' . $config->{db_conf}->{dbname} 
    . ':' . $config->{db_conf}->{dbhost};

    $dbh->disconnect() if($dbh);

    $dbh = DBI->connect($db,
			 $config->{db_conf}->{dbuser},
			 $config->{db_conf}->{dbpasswd},
		  {PrintError => 1,
		   RaiseError => 1, # I don't have to error check, even this
		   AutoCommit => $config->{db_conf}->{autocommit}} 
		    );
  $sm->{dbh} = $dbh;
  }
}

our $ltr = new KoLBot::Limiter(state_manager => $sm);
$ltr->add(queue => 'use',
	  rate  => 800,	# approximately 100 times a day
	  burst => 100);

END {
  $dbh and $dbh->disconnect();
};

###################################################################
# Real bot
###################################################################

# The real bot code.
print "$FILE starting.\n";

my $bot = KoLBot->new(config  => $bot_conf->[0],
		      items   => $vitems,
		      recipes => $recipes
		      );

# my $p = $sm->get($bot_conf->[0]->{user_id},'pstate')
#   || { mode => 'start' };

# my $asdfpstate = new PermState(%{$p});
# $sm->set($bot_conf->[0]->{user_id},'pstate',$pstate);

# Temporary state, collect here for reference

# If it dies before this point, it can't corrupt anything local
# (no writes), or anything on the kingdom (we haven't connected)
# from here down we have to be more careful.

###################################################################
# Main Loop
###################################################################
# Any HTTP or not logged-in errors will re-enter here at the top.
# The code should be aware of this

$bot->lasttime(0);

DBCONNECT: {
  eval {
    print "Connecting to DB.\n";
dbconnect();

$bot->ensure(sub {
  print "Running.\n";
  $bot->get_pwd();
 MAIN: 
  while(1){
    my @kmails = $bot->read_kmail();
    unless(@kmails) {
      my $messages = $bot->get_chat();
    MESSAGE:
      for my $m (@$messages) {
	next MESSAGE unless($m->{type} eq 'private');

	for(@{$config->{ignore_list}}) {
	  if($_ == $m->{user_id}) {
	    next MESSAGE;
	  }
	}

	print "/msg help ",$m->user,"\n";
	# we dont' talk
	$bot->send_message(new KoLBot::ChatMessage(
   message => 'I don\'t talk much.  Take a look at the help I\'m mailing you.',
						   user => $m->user,
						   type => 'private'));
	$bot->send_kmail(new KoLBot::KMail(
					   message => $helpscript,
					   user    => $m->user));
      }
      sleep($config->{delay});
      redo MAIN;
    }

  MAIL:
    while(my $kmail = shift @kmails) {

      for (@{$config->{ignore_list}}) {
	if($kmail->{user_id} == $_) {
	  print "IGNORE LIST\n";
	  print "Kmail: ",$kmail,"\n\n";
	  $bot->save_kmail($kmail);	# ALWAYS SAVE
	}
      }
	
      for (@$bot_conf) {
	if($kmail->{user_id} == $_->{user_id}) {
	  print "Internal mail\n";
	  $bot->save_kmail($kmail);	# Force not necessary
	  next MAIL;
	}
      }

      if($testing && $kmail->{user} !~ /^$test_users$/i) {
	print $kmail,"\n";
	print "Not appropriate for test mode, byebye\n";
	$bot->save_kmail($kmail);	# Force not necessary
	$bot->send_kmail(new KoLBot::KMail(
			      user => $kmail->user,
     message => 'Bot is in testing mode. '
	  . 'Your message will not be read, and any items have been treated as donations.  '
	  . 'Contact Fnord7 if this was not your wish.'
					   ),1);
	next;
      }

      # in all other cases we are dealing with a specific mail
      print "Kmail: ",$kmail,"\n\n";

      process_mail($bot, $sm, $kmail);
    }
  }}) };
  redo DBCONNECT  if($@ =~ /Lost connection to MySQL server/);
  redo DBCONNECT  if($@ =~ /MySQL server has gone away/);
  die $@ unless $config->{restart_on_fatal};
  print "FATAL ERROR, AWM, $@\n";
  sleep 3*60,
  redo DBCONNECT;
}
die "Shouldn't be here.";

sub process_mail {
  my $bot = shift;
  my $sm = shift;
  my $kmail = shift;

  my $user_id = $kmail->user_id;
  my $user_name = $kmail->user;

  # create user info if it doen't exist
  my $ustate = $sm->get($user_id,'ustate');
  unless($ustate->{user_id}) {
    $ustate = { user_id => $user_id,
		user_name => $user_name};
    $sm->set($user_id,'ustate',$ustate);

    my $users = $sm->get($bot_conf->[0]->{user_id},'users');
    $users->{lc($user_name)} = $user_id;
    $sm->set($bot_conf->[0]->{user_id},'users',$users);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  my $pmstate = $sm->get($bot_conf->[0]->{user_id},'process_mail');
  $pmstate->{mode} ||= 'start';
  $pmstate->{mail_in} ||= 0;

  if($kmail->id <= $pmstate->{mail_in}) {
    print "Finished mail ",$kmail->id,"\n";
    $bot->save_kmail($kmail);	# Force not necessary
    return;
  }

  if($pmstate->{mode} eq 'start') {
    $pmstate->{error} = 0;
    $pmstate->{message} = '';	# To construct the outgoing message
    $pmstate->{line} = 0;
    $pmstate->{next_print_line} = 0;
      
    my $uitems = $sm->get($kmail->user_id,'uitems');

    if($kmail->meat) {
      $ustate->{meat} += $kmail->meat;
      print "Adding ",$kmail->meat," meat for ",$kmail->user,"\n";
    }
    for (0..$#{$kmail->number}) {
      exists($uitems->{$kmail->item_id->[$_]})
	or $uitems->{$kmail->item_id->[$_]} = 0;
      $uitems->{$kmail->item_id->[$_]} += $kmail->number->[$_];
      print "Adding ",$kmail->number->[$_],"@",$kmail->item_id->[$_]," for ",
      $kmail->user,"\n";
    }
    $pmstate->{mode} = 'process_lines';
    # link these
    $sm->set($bot_conf->[0]->{user_id},'process_mail',$pmstate);
    $sm->set($user_id,'ustate',$ustate);
    $sm->set($user_id,'uitems',$uitems);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    # /link
    print "attachments committed\n";
  }

  if($pmstate->{mode} eq 'process_lines') {
    # mode >= process_lines
    my $message = $kmail->message;
    my @lines = split /\cj/,$message;
    (scalar @lines ) or $pmstate->{error} = 1;

    while($pmstate->{line} < @lines) {
      my $line = $lines[$pmstate->{line}];

      $pmstate->{message} .= $line . "\n";
      
      if($pmstate->{error}) {
	$pmstate->{line}++;
	next;
      }

      if($line =~ /^\s*$/){ # blank line
	$pmstate->{line}++;
	next;
      }
      
      my ($rv,$cout)
	= process_line($bot,$sm,$user_id,$line,$kmail->time);

      if($rv != 2) {
	$cout =~ s/\n/\n> /g;
	$cout =~ s/^/> /;
	$cout =~ s/\n> $/\n/;
      }
      $cout .= "> Execution finished.\n" if($rv);
      $pmstate->{message} .= $cout;
      $pmstate->{error} = $rv;
      $pmstate->{line}++;
      $sm->set($bot_conf->[0]->{user_id},'process_mail',$pmstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
      # /link
    }

    # add status
    if($config->{sescrow}) {
      unless($pmstate->{error} == 2) {
	my ($rv,$cout) = 
	  line_status($bot,$sm,$user_id);
	$pmstate->{message} .= $cout;
      }

      my ($rv,$cout) = line_incoming($bot,$sm,$user_id);
      $pmstate->{message} .= $cout;
    } else {
      $pmstate->{message} .= status($bot,$sm,$user_id);
    }
    $pmstate->{message} .= "'help' may help.";
      
    my $kmail_tot = new KoLBot::KMail;
    $kmail_tot->user($kmail->user);
    $kmail_tot->message($pmstate->{message});
    $bot->send_kmail($kmail_tot,1);

    print "Done, whew.\n\n";

    # link
    $pmstate->{mail_in} = $kmail->id;
    $pmstate->{mode} = 'start';
    $sm->set($bot_conf->[0]->{user_id},'process_mail',$pmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    # /link
    # This is not necessary, but convenient.
    $bot->save_kmail($kmail);
    return;
  }
  die "process_mail: unkonwn mode";
}

# Should build this into a CLI (!)
sub process_line {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $command = shift;
  my $time = shift;

  if($command =~ /^\s*$/i) {
    return (1);
  }

  if($command =~ /^help/i) {
    return(2,$helpscript);
  }

  my $m_user = '[\w][\w\d ]{1,28}[\w\d]|\d+';
  if($config->{sescrow}) {
    if($command =~ /^ask\s+([\d,]{1,18})\s+(.+?)\s*$/i) {
      my $amt = $1;
      my $item = $2;
      $amt =~ s/,//g;
      return line_ask($bot,$sm,$user_id,$amt,$item,$time)
	if($amt != 0);
    }
    if($command =~ /^ask\s+for\s+([\d,]{1,18})\s+(.+?)\s*$/i) {
      my $amt = $1;
      my $item = $2;
      $amt =~ s/,//g;
      return line_ask($bot,$sm,$user_id,$amt,$item,$time)
	if($amt != 0);
    }
    if($command =~ /^ask/i) {
      return (1,"Usage: ask NUMBER ITEM\nNUMBER is how many you want, "
	      . "ITEM is the name of the item or 'meat'.\n");
    }

    if($command =~ /^offer\s+($m_user)\s*$/i) {
      return line_offer($bot,$sm,$user_id,$1,$time);
    }
    if($command =~ /^offer/i) {
      return (1,"Usage: offer USER\n"
	      . "USER is the name or number of your partner\n");
    }

    if($command =~ /^notify\s*$/i) {
      return line_notify($bot,$sm,$user_id,$time);
    }

    if($command =~ /^status\s*$/i) {
      return(1,"\n");
    }
    if($command =~ /^status\s*($m_user)\s*$/i) {
      return line_status($bot,$sm,$user_id,$1,$time);
    }
    if($command =~ /^status/i) {
      return (1,"Usage: status (USER)\n"
	      . "Without a USER, give status of your outstanding offer, "
	      . "with a USER gives the status of an offer "
	      . "they have made to you\n");
    }

    if($command =~ /^accept\s+($m_user)\s+(\w+)\s*$/i) {
      return line_accept($bot,$sm,$user_id,$1,$2,$time);
    }
    if($command =~ /^accept/i) {
      return (1,"Usage: accept USER CODE\n"
	      . "Accepts a trade with USER corresponding to CODE. "
	      . "The CODE appears in the status message from 'status USER'\n");
    }

    if($command =~ /^cancel/i) {
      return line_cancel($bot,$sm,$user_id,$time);
    }

    if($command =~ /^(thank you|thanks)/i) {
      return (0,"I'll pass your well wishes on to $config->{owner}.\n");
    }

    if($command =~ /^incoming/i) {
      return line_incoming($bot,$sm,$user_id,$time);
    }

    # VERY LIMITED COMMANDS
    print "Unknown command!\n";
    return(1,"Unrecognized command.\n");
  }

  if($config->{commands}->{send}
     &&
     $command =~ /^(send|return)\s+(\d+)\s+(.*?)\s*$/i) {
    return line_send($bot,$sm,$user_id,$command,$time);
  }

  if($config->{commands}->{checkout}
     &&
     $command =~ /^checkout/i) {
    return line_checkout($bot,$sm,$user_id,$command,$time);
  }

  if($config->{commands}->{make}
     && 
#     $command =~ /^(cook|mix)\s+(\d+)\s+(.*?)\s*$/i) {
     $command =~ /^(cook)\s+(\d+)\s+(.*?)\s*$/i) {
    return line_make($bot,$sm,$user_id,$command,$time);
  }

  # should really be make
  if($config->{commands}->{create}
     && 
     $command =~ /^(create)\s+(\d+)\s+(.*?)\s*$/i) {
    return line_create($bot,$sm,$user_id,$command,$time);
  }

  if($config->{commands}->{use}
     &&
     $command =~ /^(use)\s+(\d+)\s+(.*?)\s*$/i) {
    return line_use($bot,$sm,$user_id,$command,$time);
  }

  print "Unknown command!\n";
  return(1,"Unrecognized command.\n");
}

sub line_send {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $command = shift;

  $command =~ /^(send|return)\s+(\d+)\s+(.*?)\s*$/i;

  # what's the item number?
  # use item numbers (xx) for beer!
  my $numr = $2;
  my $inamer = $3;
  my $ir;

  my $ustate = $sm->get($user_id,'ustate');
  my $uitems = $sm->get($user_id,'uitems');

  if($inamer =~ /^meat$/i) {
    $ustate->{meat} ||= 0;
    if($ustate->{meat} < $numr) {
      my $cout = '';
      print "Not enough meat.\n";
      return (1,"You have only " . $ustate->{meat}
	      . " meat, which is less than " . $numr . "\n");
    }
    my $cout;
    while($numr > $MEAT_LIMIT) {
      my($rv,$cout1) = send_item($bot,$sm,$user_id,$MEAT_LIMIT,'meat');
      $cout .= $cout1;
      $numr -= $MEAT_LIMIT;
    }
    my($rv,$cout1) = send_item($bot,$sm,$user_id,$numr,'meat');
    $cout .= $cout1;
    return(0,$cout);
  }
#  if($inamer =~ /^\((\d+)\)$|^#(\d+)$/) {
  if($inamer =~ /^\((\d+)\)$/) {
    my $idr = $1;
    unless(exists($bot->items->by_id->{$idr})) {
      print "Item not found: $inamer\n";
      return(1,"Item not found: $inamer\n");
    }
    $ir = $bot->items->by_id->{$idr};
  } elsif(exists($bot->items->by_name->{lc($inamer)})) {
    $ir = $bot->items->by_name->{lc($inamer)};
  } else {
    print "Item not found: $inamer\n";
    return(1,"Item not found: $inamer\n");
  }

  if(!exists($uitems->{$ir->id})
     || ($uitems->{$ir->id} < $numr)) {
    my $num = exists($uitems->{$ir->id}) 
      ? $uitems->{$ir->id} : 0;
    print "Not enough: $numr\@" . $ir->id ."\n";
    return(1,"Not enough items, you have $num\n");
  }

  my $cout;
  while($numr > $ITEM_LIMIT) {
    my($rv,$cout1) = send_item($bot,$sm,$user_id,$ITEM_LIMIT,$ir);
    $cout .= $cout1;
    $numr -= $ITEM_LIMIT;
    }
  my($rv,$cout1) = send_item($bot,$sm,$user_id,$numr,$ir);
  $cout .= $cout1;
  return(0,$cout);
}

sub line_checkout {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;

  my $LOCAL_STATE = 'line_checkout';
  my $lcstate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lcstate->{cout} ||= '';

  my ($rv,$cout);

  my $uitems = $sm->get($user_id,'uitems');
  my $ustate = $sm->get($user_id,'ustate');

  for (keys %{$uitems}) {
    next if ($uitems->{$_} < 1);
    while($uitems->{$_} > $ITEM_LIMIT) {
      my $item = $bot->items->get_item(id => $_)
	or die "NOITEM";
      ($rv,$cout) =
	send_item($bot,$sm,$user_id,$ITEM_LIMIT,$item);
      $lcstate->{cout} .= $cout;
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
      $uitems = $sm->get($user_id,'uitems');
    }
    my $item = $bot->items->get_item(id => $_)
      or die "NOITEM";
    ($rv,$cout) =
      send_item($bot,$sm,$user_id,$uitems->{$_},$item);
    $lcstate->{cout} .= $cout;
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }
  if($ustate->{meat}) {
    while($ustate->{meat} > $MEAT_LIMIT) {
      ($rv,$cout) =
	send_item($bot,$sm,$user_id,$MEAT_LIMIT,'meat');
      $lcstate->{cout} .= $cout;
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
      $ustate = $sm->get($user_id,'ustate');
    }

    ($rv,$cout) =
      send_item($bot,$sm,$user_id,$ustate->{meat},'meat');
    $lcstate->{cout} .= $cout;
  }

  $cout = $lcstate->{cout};
  $lcstate->{cout} = '';
  $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
  $dbh->commit unless $config->{db_conf}->{autocommit};
  return(1,$cout);
}

#  send_item($bot,$state,$data,$user,$numr,$ir);
# if this ^C or dies, it should expect re-entry
sub send_item {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $numr = shift;
  my $ir = shift;

  my $LOCAL_STATE = 'send_item';

  ref($ir) eq 'KoLBot::Item'
    or $ir eq 'meat'
    or die "Bad ir, ($ir) ref = ",ref($ir);

  my $sistate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $sistate->{mode} ||= 'start';
  my $uitems = $sm->get($user_id,'uitems');
  my $ustate = $sm->get($user_id,'ustate');

  my $cout;

  $ustate->{send_serial} ||= 0;

  SENDITEM: {
    if($sistate->{mode} eq 'start') {
      # So that if the send_kmail dies, we check rather than assuming.
      $sistate->{mode} = 'sending';
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$sistate);
      $dbh->commit unless $config->{db_conf}->{autocommit};

      if(!ref($ir)
	 && $ir eq 'meat') {
	if($ustate->{meat} < $numr) {
	  print "send_item, not enough meat\n";
	  return (1,"not enough meat\n");
	}
	$bot->send_kmail(new KoLBot::KMail(
			   user    => $user_id,
			   number  => 0,
			   item_id => 0,
			   meat => $numr,
		   message => 's'.$ustate->{send_serial}.'e',
					 ),1,1)
	  or bad_bad_stuff($bot,$sm,$user_id,$numr,$ir);
      } elsif (ref($ir) eq 'KoLBot::Item') {
	if($uitems->{$ir->id} < $numr) {
	  print "send_item, not enough items\n";
	  return (1,"not enough items\n");
	}
	print "trying to actually mail to $user_id, $numr of ",$ir->id,
	  " ",$ir->name,"\n";
	$bot->send_kmail(new KoLBot::KMail(
			   user    => $user_id,
			   number  => [$numr],
			   item_id => [$ir->id],
		   message => 's'.$ustate->{send_serial}.'e',
					 ),1,1)
	  or bad_bad_stuff($bot,$sm,$user_id,$numr,$ir);
      } else {
	die "Unknown \$ir = $ir\nref \$ir = ", ref($ir),"\n";
      }
      # If we are here, we believe the message was sent correctly,
      # on this try.
      #
      # Best: take this answer, and reconcile it with the one below.
      $sistate->{mode} = 'sent';
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$sistate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
    }

    if($sistate->{mode} eq 'sending'
       || $sistate->{mode} eq 'sent') {
      # NOW, there are a number of possibilities
      # 1. We send the item, regardless of exit,
      #    we check our outgoing messages, and it was sent.
      # 2. We send the item, no outgoing message at all.
      #    -> resend
      # 3. We get a "not enough blah" message
      #    attached: RED FLAG, RED FLAG.  Nofity them, flag their account,
      #    ignore their email until reset.

      # check, by looking at sent email, 1 page

      my @sentmail = 
	  $bot->ensure(sub {$bot->read_kmail(box => 'Outbox',
					     page_limit => 1,
					     dont_die => 1)});

      # they come newest to oldest
      for (reverse @sentmail) {
	if( ($_->user_id eq $user_id)
	    && ($_->message eq 's'.$ustate->{send_serial}.'e')) {
	  # message is there
	  
	  if( (ref $ir eq 'KoLBot::Item') 
	      && (@{$_->number} > 0)
	      && ($_->number->[0] == $numr) 
	      && ($_->item_id->[0] == $ir->id) 
	      ) {
	    # item is there 
	    # we successfully sent the item.  Stop trying.

	    $uitems->{$ir->id} -= $numr;
	    my $msg = "Sending $numr \@ " . $ir->name . " (#"
	      . $ir->id .")";
	    $cout .= $msg . "\n";
	    print "> $msg\n";
	  } elsif( ($ir eq 'meat') &&
		   ($_->meat == $numr) ) {
	    # meat is there

	    $ustate->{meat} -= $numr;
	    my $msg = "Sending $numr meat ";
	    $cout .= $msg . "\n";
	    print "> $msg\n";
	  } else {
	    # here: what the fuck?
	    warn "Send serial: ",$ustate->{send_serial},"\n";
	    if($ir eq 'meat') {
	      warn "To send: $numr meat\n";
	    } else {
	      warn "To send: $numr\@",$ir->id,"\n";
	    }
	    warn "In message, ",$_->meat," meat; ",
  	      $_->number,"\@",$_->item_id,"\n";
	    die "BAD: message was sent, item not attached?!";
	  }
	  $sistate->{mode} = 'start';
	  $ustate->{send_serial}++;
	  $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$sistate);
	  $sm->set($user_id,'ustate',$ustate);
	  $sm->set($user_id,'uitems',$uitems);
	  $dbh->commit unless $config->{db_conf}->{autocommit};
	  return (0,$cout);
	}
      }
      
      if($sistate->{mode} eq 'sent') {
	die "I thought I sent it, but I didn't";
      }

      # This is the most dangerous point, if they can
      # somehow convince us that we haven't sent the item when we
      # have, everything dies.
      # We 'know' message didn't get through.  Try it again
      print "Item did not get through, trying again\n";
      print "Item id is ", (ref($ir) ? $ir->id : $ir),"\n";
      print "Number is $numr\n";
      print "Serial is ",$ustate->{send_serial},"\n";
      $sistate->{mode} = 'start';
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$sistate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
      redo SENDITEM
    }
    die "Shouldn't be here.";
  }
  die "Shouldn't be here.";
}

sub status {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;

  my $ustate = $sm->get($user_id,'ustate');
  my $uitems = $sm->get($user_id,'uitems');
  my $uitems_l = $sm->get($user_id,'uitems_l');

  my $mout;
  $mout = "You currently have as credit:\n";
  my $limbo = 0;
  for (keys %{$uitems}) {
    next unless ($uitems->{$_} || $uitems_l->{$_});

    $mout .= $uitems->{$_};

    if($uitems_l->{$_}) {
      $mout .= '+' . $uitems_l->{$_}; # Those in limbo
      $limbo += $uitems_l->{$_};
    }

    $mout .= ' '
      . $bot->items->by_id->{$_}->name . " (#"
      . $bot->items->by_id->{$_}->id . ")\n";
  }
  if($ustate->{meat}) {
    $mout .= $ustate->{meat} . " meat\n";
  }
  if($limbo) {
    $mout .= 'You have some items in limbo (following the + sign).  '
      . "Ask Fnord7 to find these for you manually when he has the chance.\n";
  }
  return $mout;
}

#########################################################################
# sescrow
#########################################################################

# this is a kludge that works only with DBStateManager
# this should be made better, but it is a problem with the current
# structure
#
# returns array of user names of those who are offering me
# AWM
sub find_offers_to_me {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;

  unless($config->{smtype} eq 'db') {
    die "Fatal, can't find offers if state managers other than DB now.\n";
  }

  my $ustate = $sm->get($user_id,'ustate');

  my $MANAGED_STATE_TABLE = $config->{db_conf}->{table_pre} . 'managed_state';
  my $sql = qq{
    SELECT id FROM $MANAGED_STATE_TABLE 
      WHERE class='trans' AND var_name='player_to' AND var_value = ?
    };
  my $sth = $dbh->prepare($sql);
  my $rv = $sth->execute($user_id);
  if($rv < 1) {
    return ();
  }
  my $vals = $sth->fetchall_arrayref({});
  my @offerrers=();

  for(@{$vals}) {
    push @offerrers,$_->{id};
  }
  return @offerrers;
}

sub line_ask {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $number = shift;
  my $item_arg = shift;

  my $item;
  if(lc($item_arg) eq 'meat') {
    $item = 'meat';
  } else {
    my $arg_type = 'name';
    $arg_type = 'id' if($item_arg =~ /^\d+$/);
    $item = $bot->items->get_item($arg_type => lc($item_arg));
  }

  unless($item) {
    print "don't know item $item_arg\n";
    return(1,"I don't know the item '$item_arg'.\n");
  }

  my $trans = $sm->get($user_id,'trans');
  my $asks = $sm->get($user_id,'asks');

  # AWM gone once we split off
  if($trans->{player_to}) {
    print "Already offered to '$trans->{player_to}'\n";
    return(1,"You have already offered this to '$trans->{player_to}', "
	   . "you can't ask for more now.  You can cancel.\n");
  }

  if(!ref($item)
     && $item eq 'meat') {
    $asks->{meat} = $number;
  } else {
    $asks->{$item->id} = $number;
  }

  $sm->set($user_id,'asks',$asks);
  $dbh->commit unless $config->{db_conf}->{autocommit};
  return(0,"Added to offer.\n");
}

sub line_incoming {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $time = shift;

  my @offerrers = find_offers_to_me($bot,$sm,$user_id);
  my $cout = '';

  unless(@offerrers) {
    return(1,"Noone is offering you anything at this time.\n");
  }

  $cout .= "I have offers for you from these nice people:\n";

  print "listing incoming offers:";
  for(sort @offerrers) {
    print " $_,";
    my $ystate = $sm->get($_,'ustate');
    $cout .= $ystate->{user_name} . " (#" . $_ . ")\n";
  }
  print "\n";

  $cout .= "\nTo look at them, use 'status name'\n";

  return(2,$cout);
}

sub line_notify {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $time = shift;

  my $ustate = $sm->get($user_id,'ustate');
  $ustate->{notify} ||= 0;
  $ustate->{notify} = !($ustate->{notify});
  $sm->set($user_id,'ustate',$ustate);
  my $cout;
  if($ustate->{notify}) {
    $cout = "I will now let you know when offers are made.\n";
  } else {
    $cout = "I won't send you unsolicited mail.\n";
  }
  print $cout;
  return(0,$cout);
}

sub line_offer {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $drc = shift;
  my $time = shift;

  my $ustate = $sm->get($user_id,'ustate');
  my $uitems = $sm->get($user_id,'uitems');
  $ustate->{meat} ||= 0;

  if($drc !~ /^\d+$/) {
    my $users = $sm->get($bot_conf->[0]->{user_id},'users');
    unless (exists($users->{lc($drc)})) {
      print "Until they are in the database, you must use player number.\n";
      return(1,"OFFER NOT MADE\n\n"
	     . "I'm sorry, but until they are in the database,"
	     . "you must use player number.\n");
    }
    $drc = $users->{lc($drc)};
  }

  if($drc == $user_id) {
    my $msg = "Sorry, you don't need me to trade with yourself.\n";
    print $msg;
    return(1,$msg);
  }

  my $trans = $sm->get($user_id,'trans');
  my $asks = $sm->get($user_id,'asks');

  my $tot;
  $tot++  if $asks->{meat};
  for(keys %{$asks}) {
    $tot++ if $asks->{$_};
  }
  unless($tot) {
    print "Tried to offer without asking for anything.\n";
    return(1,"Sorry donations are not allowed, you must ask for something.\n"
	   . " OFFER NOT MADE\n");
  }

  $tot = 0;
  $tot++ if $ustate->{meat};
  for(values %{$uitems}) {
    $tot += $_;
  }
  unless($tot > 0) {
    print "Didn't offer anything\n";
    return(1,"You must send me items or meat to offer them. "
	   . "You haven't offered any items or meat, "
	   . "you can't make an empty offer.\n"
	   . "OFFER NOT MADE\n");
  }
  
  my $LOCAL_STATE = 'line_offer';
  my $lostate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lostate->{number} ||= 0;
  
  my @letters = split(//,
	  'ABCDEFGHJKLMPQRSTUVWXY23456789');

  # give it a random phrase so that it is harder to scam.
  $trans->{code} = $letters[rand(@letters)] . $letters[rand(@letters)] 
    . $letters[rand(@letters)] . $letters[rand(@letters)];
  $trans->{player_to} = $drc;
  $trans->{player_from} = $user_id;
  $trans->{offer_time} = $time;
  $trans->{number} = $lostate->{number};

  $lostate->{number}++;

  $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lostate);
  $sm->set($user_id,'trans',$trans);
  $dbh->commit unless $config->{db_conf}->{autocommit};

  my $ystate = $sm->get($drc,'ustate');
  if($ystate
     && $ystate->{notify}) {
    print "Send email to trading partner\n";
    my ($rv,$cout) = line_status($bot,$sm,$drc,$user_id,$time);
    $bot->send_kmail(new KoLBot::KMail(
	       user => $drc,
	       message => $ustate->{user_name}
			       ." has offered to trade with you.  Details:\n"
			       . $cout,
	       number => [0],
	       ),1);
  }

  return(1,"Offered to ".$trans->{player_to}."\n");
}

sub line_status {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $drc = shift;
  my $time = shift;

  my $cout = '';

  my $asks = $sm->get($user_id,'asks');
  my $trans = $sm->get($user_id,'trans');

  if($trans->{player_to} && $drc) {
    print "Own offer in progress\n";
    $cout .= "\nYou cannot respond to another offer until your own offer "
      . "has been accepted or cancelled.\n"
      . "Use 'status' with no argument to ask about your own offer.\n\n";
  }

  my $ustate = $sm->get($user_id,'ustate');
  my $uitems = $sm->get($user_id,'uitems');

  if($drc){
    if($drc !~ /^\d+$/) {
      my $users = $sm->get($bot_conf->[0]->{user_id},'users');
      unless($users->{lc($drc)}) {
	print "Didn't match in users\n";
	return(1,"'$drc' hasn't made you an offer at this time.\n");
      }
      $drc = $users->{lc($drc)};
    }

    my $drustate = $sm->get($drc,'ustate');
    my $drname = $drustate->{user_name};

    $asks = $sm->get($drc,'asks');
    $trans = $sm->get($drc,'trans');
    if(!$trans->{player_to}
       ||
       $user_id ne $trans->{player_to}) {
      print "$drc hasn't offered\n";
      return(1,"'$drname' hasn't made you an offer at this time.\n");
    }

    $cout .= "Transaction number " . $trans->{number} . "\n";
    $cout .= "Acceptance code: $trans->{code}\n";

    my $ystate = $sm->get($drc,'ustate');
    my $yitems = $sm->get($drc,'uitems');
    $ystate->{meat} ||= 0;

    $cout .= "----------\n$drname (#$drc) is offering:\n";
    for(keys %$yitems) {
      next unless $yitems->{$_};
      $cout .= add_commas($yitems->{$_}) 
	. ' ' . $bot->items->get_item(id => $_)->name
	. " (#" . $_ . ")\n";
    }
    if($ystate->{meat}){
      $cout .= add_commas($ystate->{meat}) . " meat\n";
    }
    $cout .= "----------\n";

    my %tuitems = %$uitems;

    $cout .= "$drname (#$drc) is asking for:\n";
    for(keys %$asks) {
      next unless $_ =~ /^\d+$/;
      next unless $asks->{$_};
      $cout .= add_commas($asks->{$_}) . " (" . add_commas($tuitems{$_}||0) 
	.') ' . $bot->items->get_item(id => $_)->name
	. " (#" . $_ . ")\n";
      delete($tuitems{$_});
    }
    $ustate->{meat} ||= 0;
    $asks->{meat} ||= 0;
    $cout .= add_commas($asks->{meat}) . " (" . add_commas($ustate->{meat}) 
      . ") meat\n"
      . "Your credit shown in parentheses.\n"
      . "----------\n";

    my $cout1 = '';
    for(keys(%tuitems)) {
      next unless $tuitems{$_};
      $cout1 .= add_commas($tuitems{$_}) . '@' 
	. $bot->items->get_item(id => $_)->name
	. " (#" . $_ . ")\n";
    }
    if($cout1) {
      my $cout2 =  "You have sent these extra items\n";
      $cout2 .= $cout1;
      $cout2 .= "These will be returned to you if you accept the offer.\n";
      $cout2 .= "----------\n";
      $cout .= $cout2;
    }

    return(2,$cout);
  }
      
  $cout .= "----------\n";

  my $msg = '';
  for(keys %$asks) {
    next unless $asks->{$_};
    next unless $_ =~ /^\d+$/;
    $msg .= add_commas($asks->{$_}) . ' ' 
      . $bot->items->get_item(id => $_)->name
      . " (#" . $_ . ")\n";
  }
  if($asks->{meat}) {
    $msg .= add_commas($asks->{meat}) . " meat\n";
  }

  my $ystate = {};
  if($trans->{player_to}) {
    $ystate = $sm->get($trans->{player_to},'ustate');
  }

  if($msg eq '') {
    $cout .= "You aren't asking for anything.\n";
  } else {
    if($trans->{player_to}) {
      $cout .= "Transaction number: " . add_commas($trans->{number}) . "\n";
      if($ystate->{user_name}) {
	$cout .= "Asking $ystate->{user_name} (#$trans->{player_to}) for:\n";
      } else {
	$cout .= "Asking #$trans->{player_to} for:\n";
      }
    } else {
      $cout .= "Planning to ask for:\n";
    }
    $cout .= $msg;
  }
  $cout .= "----------\n";

  $msg = '';
  my $tot = 0;
  if($trans->{player_to}) {
    if($ystate->{user_name}) {
      $msg .= 
	"You are offering $ystate->{user_name} (#$trans->{player_to}):\n";
    } else { 
      $msg .= "You are offering #$trans->{player_to}:\n";
    }
  } else {
    $msg .= "Your credit:\n";
  }

  for (keys %{$uitems}) {
    next unless ($uitems->{$_});
    $tot ++;

    $msg .= add_commas($uitems->{$_});

    $msg .= ' '
      . $bot->items->by_id->{$_}->name . " (#"
      . $bot->items->by_id->{$_}->id . ")\n";
  }
  if($ustate->{meat}) {
    $tot ++;
    $msg .= add_commas($ustate->{meat}) . " meat\n";
  }
  if($tot>0) {
    $cout .= $msg;
  } else {
    $cout .= "You have no credit.\n";
  }
  $cout .= "----------\n";

  return(2,$cout);
}

sub line_cancel {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;

  # cancel current transaction.
  $sm->set($user_id,'asks',{});
  $sm->set($user_id,'trans',{});
  $dbh->commit unless $config->{db_conf}->{autocommit};

  line_checkout($bot,$sm,$user_id);
}

sub line_accept {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $drc = shift;
  my $code = shift;
  my $time = shift;

  my $trans = $sm->get($user_id,'trans');
  if($trans->{player_to}) {
    print "Can't deal with other transactions while one of yours is open.\n";
    return(1,"You have offered, you can't deal with other transactions "
	   . "until you complete or cancel it.\n");
  }

  my $LOCAL_STATE = 'line_accept';
  my $lastate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lastate->{mode} ||= 'start';
  my $ustate = $sm->get($user_id,'ustate');

  my $drname = $drc;
  if($drc !~ /^\d+$/) {
    my $users = $sm->get($bot_conf->[0]->{user_id},'users');
    unless($users->{lc($drc)}) {
      print "Didn't match in users\n";
      return(1,"'$drc' hasn't made you an offer at this time.\n");
    }
    $drc = $users->{lc($drc)};
  }

  $trans = $sm->get($drc,'trans');
  my $asks = $sm->get($drc,'asks');

  if($lastate->{mode} eq 'start') {
    if(!$trans 
       ||
       $user_id != $trans->{player_to}) {
      print "Haven't offered you\n";
      return(1,"'$drc' hasn't made you an offer at this time.\n");
    }

    # check that the codes match

    unless(lc($code) eq lc($trans->{code})) {
      print "Codes don't match. $trans->{code}\n";
      my ($rv,$cout) = line_status($bot,$sm,$user_id,$drc,$time);
      return(1,"Codes don't match, the deal may have been re-offered.\n"
	     . "Check the status carefully:\n".$cout);
    }

    # check that what you have is what they offered.

    my $uitems = $sm->get($user_id,'uitems');

    for(keys %$asks) {
      next unless $asks->{$_};
      next if ($_ eq 'meat');
      $uitems->{$_} ||= 0;
      if($uitems->{$_} < $asks->{$_}) {
	my $msg = "You have been asked for ",add_commas($asks->{$_})
	  . ' ' . $bot->items->get_item(id=>$_)->name . " (#" . $_ . "). "
	  . "You have provided "
	  . add_commas($uitems->{$_}) . ", send more.\n";
	print $msg;
	my ($rv,$cout) 
	  = line_status($bot,$sm,$user_id,$drname,$time);
	$msg .= $cout;
	return(1,$msg);
      }
    }

    $ustate->{meat} ||= 0;
    $asks->{meat} ||= 0;
    if($ustate->{meat} < $asks->{meat}) {
      my $msg = "You have provided " . add_commas($ustate->{meat}) . " meat. " 
	. "They have asked for " . add_commas($asks->{meat}) 
	. ". Please send more.\n";
      print $msg;
      return(1,$msg);
    }
    $lastate->{mode} = 'good';
    my ($rv,$cout,$cout1);
    ($rv,$cout) = line_status($bot,$sm,$user_id,$drname,$time);
    ($rv,$cout1) = line_status($bot,$sm,$drc);
    $lastate->{cout} = "Thank you for using escrowbot!\n\n"
      . "Please send any comments, good or bad, to Fnord7.\n\n"
      . "Offer accepted, for your records:\n" . $cout;
    $lastate->{cout1} = "Thank you for using escrowbot!\n\n"
      . "Please send any comments, good or bad, to Fnord7.\n\n"
      . "Offer accepted, for your records:\n" . $cout1;
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lastate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }
  # it is good.  easy option: switch stuff.

  if($lastate->{mode} eq 'good') {
    $ustate = $sm->get($user_id,'ustate');
    my $uitems = $sm->get($user_id,'uitems');
    my $ystate = $sm->get($drc,'ustate');
    my $yitems = $sm->get($drc,'uitems');

    my $offers = {%$yitems};	# Later this may be separate, like asks
    $ystate->{meat} ||= 0;
    $offers->{meat} = $ystate->{meat};

    for(keys %$offers) {
      next unless $_ =~ /\d+/;
      $yitems->{$_} ||= 0;
      $uitems->{$_} ||= 0;
      $yitems->{$_} -= $offers->{$_};
      $uitems->{$_} += $offers->{$_};
    }
    $ystate->{meat} -= $offers->{meat};
    $ustate->{meat} += $offers->{meat};

    for(keys %$asks) {
      next unless $_ =~ /\d+/;
      $yitems->{$_} ||= 0;
      $uitems->{$_} ||= 0;
      $yitems->{$_} += $asks->{$_};
      $uitems->{$_} -= $asks->{$_};
    }
    $ystate->{meat} += $asks->{meat};
    $ustate->{meat} -= $asks->{meat};

    # save them
    $sm->set($drc,'asks',{});
    $sm->set($drc,'trans',{});
    $lastate->{mode} = 'sending';
    $sm->set($user_id,'ustate',$ustate);
    $sm->set($user_id,'uitems',$uitems);
    $sm->set($drc,'ustate',$ystate);
    $sm->set($drc,'uitems',$yitems);
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lastate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  if($lastate->{mode} eq 'sending') {
    line_checkout($bot,$sm,$user_id);
    line_checkout($bot,$sm,$drc);
    $bot->send_kmail(new KoLBot::KMail(
	       message => $lastate->{cout1},
	       user    => $drc,
	       item_id => 0,
	       number  => 0,
				       ),1,1);

    $lastate->{mode} = 'start';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lastate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    return(1,$lastate->{cout});
  }
  die "shouldn't be here";
}


#########################################################################
# Chefbot stuff
#########################################################################

# making/cooking is unsecure
sub line_make {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $command = shift;

  my $uitems = $sm->get($user_id,'uitems');
  my $ustate = $sm->get($user_id,'ustate'); 

  my $LOCAL_STATE = 'line_make';
  my $lmstate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lmstate->{mode} ||= 'start';

  # $command =~ /^(cook|mix)\s+(\d+)\s+(.*?)\s*$/i;
  $command =~ /^(cook)\s+(\d+)\s+(.*?)\s*$/i;
  # find the target item id.
  my $com = $1;
  my $preps = int $2;
  my $iname = lc($3);

  if(!exists($bot->items->by_name->{$iname})) {
    print "don't know item.\n";
    return(1,"I don\'t know item $iname\n");
  }
  my $id = $bot->items->by_name->{$iname}->id;

  # do we have a recipe for cooking this?
  my $r = $bot->recipes->by_product_action->{$id}->{lc($com)}->[0];
  if(!$r){
    print "No recipe for $iname ($id)\n";
    return (1,"No recipe to $com $iname ($id)\n");
  }

  if($lmstate->{mode} eq 'start') {
    $lmstate->{reps} = $preps;
    $lmstate->{mode} = 'send_away';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  # do you have the credit?
  my %chckr;
  for(@{$r->reagents}) {
    $chckr{$_} = $uitems->{$_} || 0;
    $chckr{$_} -= $lmstate->{reps};
    if($chckr{$_} < 0) {
      print "Not enough ",$bot->items->by_id->{$_}->name,"\n";
      $lmstate->{cout} = '';
      $lmstate->{mode} = 'start';
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
      return (1,"You don\'t have enough " 
	      . $bot->items->by_id->{$_}->name .".\n");
    }
  }

  # I think that's everything that can keep us from cooking.

  my $away = 0;
  my $cookbot = $bot;

  # Find the right bot with wich to make
  # AWM make this part of recipe
  for(@{$r->{reagents}}) {
    if($_ == 346) {
      $away = 1;
      $cookbot = KoLBot->new(config => $bot_conf->[1],
			     items=>$vitems,
			     recipes=>$recipes);
    }
  } 

  if($lmstate->{mode} eq 'send_away' && $away == 0) {
    $lmstate->{mode} = 'cook';
  }

  if($lmstate->{mode} eq 'send_away') {
    # if we HTML error in either of the commands, we won't know what's up
    $lmstate->{mode} = 'broken';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    for(@{$r->{reagents}}) {
      print "Sending ",$_," to cookbot\n";
      $bot->send_kmail(new KoLBot::KMail(
				 message => "Sending on behalf of " 
				       . $user_id,
			       user    => $cookbot->config->{user},
			       item_id => [$_],
			       number  => [$lmstate->{reps}],
				       ),0,1)
      or die "Bot says: ",$bot->{error};
    }
    $lmstate->{mode} = 'cook';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  if($lmstate->{mode} eq 'cook') {
    $cookbot->login()    if($away);

    my $name = $bot->items->by_id->{$id}->name;
    my $msg = "Making ($com) {" . $cookbot->config->{class} . "} " 
      . $lmstate->{reps} . " \@ $name (#" . $r->products->[0] . ")";
    $lmstate->{cout} .= $msg . "\n";
    print "> $msg";

    $uitems->{$r->products->[0]} ||= 0;
    while ($lmstate->{reps}) {
      $lmstate->{reps} --;
      $uitems->{$r->reagents->[0]} --;
      $uitems->{$r->reagents->[1]} --;
      $uitems->{$r->{products}->[0]} ++;
      # Commit before cooking, better to do one less irreversible step
      $sm->set($user_id,'uitems',$uitems);
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};

      if($com =~ /cook/i) {
	$cookbot->cook($r->reagents->[0],$r->reagents->[1]);
      } elsif($com =~ /mix/i) {
	$cookbot->mix($r->reagents->[0],$r->reagents->[1]);
      } else {
	die "Command shouldn't have matched.";
      }

      print ".";
    }
    print ".\n";

    $lmstate->{mode} = 'return_items' if $away;
    $lmstate->{mode} = 'done' unless $away;
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  if($lmstate->{mode} eq 'return_items') {
    $lmstate->{mode} = 'broken';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    print "Sending back product\n";

    $cookbot->send_kmail(new KoLBot::KMail(
					message => "Sending on behalf of " 
					. $user_id,
					user    => $bot->config->{user},
					item_id => [$r->products->[0]],
					number  => [$preps],
					),1)
      or die "Bot says: ",$bot->{error};
    $cookbot->logout();
    $lmstate->{mode} = 'done';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  if($lmstate->{mode} eq 'done') {
    my $cout = $lmstate->{cout};
    $lmstate->{cout} = '';
    $lmstate->{mode} = 'start';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lmstate);
    $sm->set($user_id,'uitems',$uitems);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    return(0,$cout);
  }

  die "line_make broken somewhere";
}

# is insecure: use security checker!
# prototype for more secure item manipulations
sub line_use {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $command = shift;
  my $time = shift;

  # process arguments, at most two items.
  $command =~ /^(use)\s+(\d+)\s+(.*?)\s*\+\s*(.*?)?$/i;
  my $action = lc($1);
  my $rreps = $2;
  my @argps = $3;
  push @argps,$4  if($4);

  my @args;
  for my $i(0..$#argps) {
    my $arg_type = 'name';
    $arg_type = 'id' if($argps[$i] =~ /^\d+$/);
    $args[$i] = $bot->items->get_item($arg_type => lc($argps[$i]));
  }

  my $LOCAL_STATE = 'line_use';
  my $lustate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lustate->{mode} ||= 'start';
  my $uitems = $sm->get($user_id,'uitems');
  my $uitems_l = $sm->get($user_id,'uitems_l');

  if($lustate->{mode} eq 'start') {
    $lustate->{cout}= '';

    if(($action eq 'use') 
       && (@args != 1)) {
      $lustate->{cout} .= "use takes one and only one item type to use\n";
      print "use takes one and only one item type to use\n";
      return (1,$lustate->{cout});
    }

    for my $i (0..$#args) {
      unless($args[$i]) {
	$lustate->{cout} .= "Item not known: $args[$i]\n";
	print "Item not found: $argps[$i]\n";
	return (1,$lustate->{cout});
      }
    }

    # AWM need to continue here making it general for other types.

    if (@{$recipes->by_reagent_action->{$args[0]->id}->{'use'}}>1) {
      $lustate->{cout} 
        .= "The action you have asked for is not uniquely defined.\n";
      print "Too many recipes ",$args[0]->id,"\n";
      return (1,$lustate->{cout});
    }

    if(@{$recipes->by_reagent_action->{$args[0]->id}->{'use'}}<1) {
      $lustate->{cout} .= "I've got no recipes that work like that.\n";
      print "Can't use: ",$args[0]->id,"\n";
      return (1,$lustate->{cout});
    }

    if(!exists($uitems->{$args[0]->id})
       || ($uitems->{$args[0]->id} < $rreps)) {
      $lustate->{cout} .= "You only seem to have "
	. $uitems->{$args[0]->id} . "\n";
      print "Not enough: $rreps\@",$args[0]->id,".\n";
      return (1,$lustate->{cout});
    }

    # check the limiter
    $lustate->{reps} = $ltr->check(user   => $user_id,
				   queue  => 'use',
				   number => $rreps,
				   time => $time);
    if($lustate->{reps} == 0) {
      $lustate->{cout} .= "You are out of uses today.  Try later.\n";
      print "No more uses\n";
      return (1,$lustate->{cout});
    }
    $lustate->{mode} = 'allowed';
    my $msg = "Using " . $lustate->{reps} ." \@ ".$args[0]->name 
      . " (" . $args[0]->id . ")";
    $lustate->{cout} .= $msg . "\n";
    print $msg;
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lustate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  my $r = $recipes->by_reagent_action->{$args[0]->id}->{'use'}->[0];

  if(@{$r->reagents} != 1) {
    $lustate->{cout} .=  "Bad use recipe\n";
    warn "Bad use recipe, \@{\$r->reagents} = " . @{$r->reagents} . "\n";
    return (1,$lustate->{cout});
  }

  if($lustate->{mode} eq 'allowed') {
    while ($lustate->{reps}) {

      # Move the items to a "limbo" state
      $lustate->{reps}--;
      for(@{$r->{reagents}}){
	$uitems->{$_} --;
	$uitems_l->{$_} ||= 0;
	$uitems_l->{$_} ++;
      }
      $sm->set($user_id,'uitems',$uitems);
      $sm->set($user_id,'uitems_l',$uitems_l);
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lustate);
      $dbh->commit unless $config->{db_conf}->{autocommit};

      # let it happen
      my ($rv,$text,@prods) = eval {
	# AWM generalize to apply recipe
	$bot->do_recipe($r,1);
      };
      if($@) {
	print "die from bot->use_item, $@\n";
	$lustate->{cout} 
	  .= "Error occured in that command, you may have ghost items.\n";
	next;
      }

      if(!$rv) {
	print "error from bot->use_item, ",$bot->{error},"\n";
	$lustate->{cout} 
	  .= "Error occured in that command, you may have ghost items.\n";
	next;
      }
      print '.';

      # Move the items to the finished state
      for(@{$r->{reagents}}){
	$uitems_l->{$_} --;
      }
      for(@prods){
	$uitems->{$_} ||= 0;
	$uitems->{$_} ++;
      }

      $sm->set($user_id,'uitems',$uitems);
      $sm->set($user_id,'uitems_l',$uitems_l);
      $dbh->commit unless $config->{db_conf}->{autocommit};
    }
    print "\n";

    my $cout = $lustate->{cout};
    $lustate->{cout} = '';
    $lustate->{mode} = 'start';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lustate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    return(0,$cout);
  }

  die 'line_use: unknown lustate->{mode}';
}

# is insecure, AWM unchecked
sub line_create {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $command = shift;
  my $time = shift;

  $command =~ /^(create)\s+(\d+)\s+(.*?)\s*$/i;
  my $rreps = $2;

  my $LOCAL_STATE = 'line_create';

  my $lcstate = $sm->get($bot_conf->[0]->{user_id},$LOCAL_STATE);
  $lcstate->{mode} ||= 'start';
  my $ustate = $sm->get($user_id,'ustate');
  my $uitems = $sm->get($user_id,'uitems');
  my ($id,$name);

  if($lcstate->{mode} eq 'start') {
    $lcstate->{cout}= '';

    # AWM: allow (xxx) here or take to a generic routine
    # what's the item number?
    if(!exists($bot->items->by_name->{lc($3)})) {
      $lcstate->{cout} .= "Item not known: $3\n";
      print "Item not found: $3\n";
      return (1,$lcstate->{cout});
    }

    $id = $bot->items->by_name->{lc($3)}->id;
    $name = $bot->items->by_name->{lc($3)}->name;

# AWM
#     if(!exists($usables->{$id})) {
    if($name eq 'dense meat stack') {

      if($ustate->{meat} < 1000*$rreps) {
	$lcstate->{cout} .= "You only seem to have "
	  . $ustate->{meat} . "\n";
	print "Not enough: $rreps\@$id.\n";
	return (1,$lcstate->{cout});
      }
    } elsif($name eq 'meat paste') {
      if($ustate->{meat} < 10000*$rreps) {
	$lcstate->{cout} .= "You only seem to have "
	  . $ustate->{meat} . "\n";
	print "Not enough: $rreps\@$id.\n";
	return (1,$lcstate->{cout});
      }
    } else {
      $lcstate->{cout} .= "Cannot create item $name\n";
      print "Can't create: $name\n";
      return (1,$lcstate->{cout});
    }


# # AWM
# #   if(!exists($uitems->{$id})
# #      || ($uitems->{$id} < $rreps)) {

    # check the limiter
#     $lcstate->{reps} = $ltr->check(creater   => $user_id,
# 				   queue  => 'create',
# 				   number => $rreps,
# 				   time => $time);
    $lcstate->{reps} = $rreps;

    if($lcstate->{reps} == 0) {
      $lcstate->{cout} .= "You are out of creates today.  Try later.\n";
      print "No more creates\n";
      return (1,$lcstate->{cout});
    }
    $lcstate->{mode} = 'allowed';
    my $msg = "Creating " . $lcstate->{reps} ." \@ $name ($id)";
    $lcstate->{cout} .= $msg . "\n";
    print $msg;
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
  }

  my $item = $bot->items->by_name->{lc($3)};

  if($lcstate->{mode} eq 'allowed') {
    while ($lcstate->{reps}) {
      eval {
	$bot->create_item($item->id,1);
	print '.';
      };
      # even an HTTP error may kill us, we can't verify action.
      ($@ eq '') or die "FATAL: creating items, may be off, $@";

      my ($meatdel,$itemdel);
      if($name eq 'dense meat stack') {
	$meatdel = 1000;
	$itemdel = 1;
      } elsif($name eq 'meat paste') {
	$meatdel = 10000;
	$itemdel = 1000;
      }

      $lcstate->{reps}--;
      $ustate->{meat} -= $meatdel;
      $uitems->{$item->id} += $itemdel;
      $sm->set($user_id,'uitems',$uitems);
      $sm->set($user_id,'ustate',$ustate);
      $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
      $dbh->commit unless $config->{db_conf}->{autocommit};
    }
    print "\n";

    my $cout = $lcstate->{cout};
    $lcstate->{cout} = '';
    $lcstate->{mode} = 'start';
    $sm->set($bot_conf->[0]->{user_id},$LOCAL_STATE,$lcstate);
    $dbh->commit unless $config->{db_conf}->{autocommit};
    return(0,$cout);
  }
  die 'line_create: unknown lcstate->{mode}';
}

sub set_profile {
  my $bot = shift;

  my $location = 'ONLINE';
  $location = 'TESTING, MAY BE DOWN' if $testing;
  $bot->account_profile( age => '0',
			 gender => '1',
			 location => $location,
			 website  => 'http://www.brandybuck.com/kolbot/',
			 quote => "A chefbot is me! Notify " 
			   . $config->{owner} . " of any problems, please.",
			   );
}

sub add_commas {
  my $number = shift;

  my $string = '' . $number;

  my @chars = split('',$string);

  my $i = $#chars - 2;
  while($i>0) {
    splice @chars,$i,0,',';
    $i -= 3;
  }

  return join('',@chars);
}

sub bad_bad_stuff {
  my $bot = shift;
  my $sm = shift;
  my $user_id = shift;
  my $numr = shift;
  my $ir = shift;

  print "BOT SAYS ",$bot->{error},"\n";

  $bot->send_kmail(new KoLBot::KMail(
		     user    => $user_id,
     message => 'Sorry, I tried to send you an item I thought I had, but '
	     . 'didn\'t.  I\'m going offline to keep my state from getting '
	     . 'meesed up further, ' . $config->{owner} 
	     . ' will have to sort it '
	     . 'out by hand.  If you want to contact her or him, you can.', 
				     ),1);
  die "SIGH";
}
