/* generic shop equipped with LSC parser */
/* written by square@imperial.mud.lp	 */
#include <lsc.h>

#include <mudlib.h>
#include <move.h>
#include <money.h>

inherit LSC;

inherit ROOM ;
inherit COINVALUE ;

#define NOWEALTH 0
#define NOCHANGE 1

static object office;
static object storeroom;
static object shopkeeper;
static object last_customer;
static object customer;
static object item;		/* goods being asked/valued/bought/sold */
static int item_value;		/* estimated true value */
static int desired_buy_price;	/* relative to the shop, not customer */
static int desired_sell_price;
static int fail_reason;
static int *give_coins, *get_coins; /* again, relative to the shop */

static mapping cost;		/* object in storeroom->value in copper units */

mapping wealth;
string owner;
string * money_types ;
int * type_value ;
int money_type_size;

int partial_wealth(mapping w, int level);
int sum(int * array, int size);
int _end();
int _show_goods();
string Convert_amount_string(int value);
int read_lsc_file();

void create() {
	seteuid(getuid());
	lsc2::create();
	Reset();

	wait_time = 1;
	_speed = 6;		/* shop code shouldn't be run fast */
	_max_ch_size = 0;	/* can't spawn child processes */
	cost = ([]);
	wealth = (["platinum":2, "gold":10, "silver":8, "copper":100]);
	set("light",10);
	set("volume", 10000000);
	set("capacity", 10000000);
	set("long",
		"This is a generic shop.\n"+
		"commands: list, value, ask, buy, sell\n");

	storeroom = clone_object(STORAGE);
	shopkeeper = clone_object("obj/orc");
	shopkeeper->set_name("The shop keeper");
	shopkeeper->set("short", "A shop keeper");
	shopkeeper->set("id", ({"shop keeper", "keeper", "man"}) );
	shopkeeper->move(this_object());
	customer = 0;
	money_types = ({ "copper", "silver", "gold","platinum" });
	type_value = ({ 1, 10, 100, 1000 });
	money_type_size = sizeof(money_types);
	give_coins = allocate(money_type_size);
	get_coins = allocate(money_type_size);
	read_lsc_file();
}

/***************************************************************/
/* it returns the best coin combination extractable from the   */
/* wealth mapping w1 to w2. If it's impossible, 0 is returned. */
/* c1 and c2 are the capacity of the 2 wealths mapping.	       */
/* change is indicated by negative number in its slot	       */
/***************************************************************/
int * coin_combination(
int amount,
mapping w1,
mapping w2,
int c1,int c2,
int level);

int * best_coin_combination(int amount, mapping w1, mapping w2, int c1,int c2) {
	fail_reason = NOCHANGE;
	if (!w1 || !w2) return 0;
	return coin_combination(amount, w1, w2, c1, c2, money_type_size);
}

/********************************************************/
/* it returns the coin combination extractable from the	*/
/* wealth mapping w. If it's impossible, 0 is returned.	*/
/********************************************************/
int * coin_combination(
int amount,
mapping w1,
mapping w2,
int c1,int c2,
int level)
{
	int * coins, i, tmp;
	int pay_coins;
	int  * alternative;

	if (partial_wealth(w1, level) < amount) {
		if (level==money_type_size) fail_reason = NOWEALTH;
		return 0;
	}
	coins = allocate(i=level);
	while(i--) coins[i] = 0;
	if (!amount) return coins;

	pay_coins = amount / type_value[level-1];

	if ( w1[money_types[level-1]] < pay_coins) {
		pay_coins = w1[money_types[level-1]];
	}

	if (alternative =
		coin_combination(amount-pay_coins*type_value[level-1],
			w1, w2, c1+pay_coins, c2-pay_coins, level-1)) {
		tmp=sum(alternative,level-1)+pay_coins;
		if (tmp>0) {
		 	if (tmp <= c2)
				return alternative + ({ pay_coins });
		} else {
			if (-tmp <= c1)
				return alternative + ({ pay_coins });
		}
	}

	if (w1[money_types[level-1]]==pay_coins)
		return 0;

	/* see if w2 can make change */
	if (alternative =
		coin_combination((pay_coins+1)*type_value[level-1]-amount,
			w2, w1, c1+pay_coins+1, c2-pay_coins-1, level-1)) {
		for(i=0;i<level-1;i++) alternative[i] = -alternative[i];
		tmp = pay_coins + 1 + sum(alternative, level-1);
		if (tmp>0) {
			if (tmp <= c2)
				return alternative + ({ pay_coins + 1});
		} else {
			if (-tmp <= c1)
				return alternative + ({ pay_coins + 1});
		}
	}

	/* oh well, not possible then */
	return 0;
}

int partial_wealth(mapping w, int level) {
	int i,ret,tmp;
	string tmpstr;
	ret = 0;
	if (level > money_type_size) level = money_type_size;
	for(i = 0; i<level ;i++) {
		ret += type_value[i] * w[money_types[i]];
	}
	return ret;
}

int sum(int * array, int size) {
	int ret,i;
	if (!size) size = sizeof(array);
	ret = 0;
	while(size--)  ret+=array[size];
	return ret;
}

remove() {
	if(shopkeeper) shopkeeper->remove();
	if(storeroom) storeroom->remove();
	::remove();
}

void init() {
	add_action ("list", "list") ;
	add_action ("value", "value") ;
	add_action ("ask", "ask") ;
	add_action ("buy", "buy") ;
	add_action ("sell", "sell") ;
	add_action ("enter_storeroom", "storeroom") ;
	add_action ("enter_office", "office");
}

int enter_storeroom() {
	mixed owners;
	if (!storeroom) {
		notify_fail("where is the storeroom?\n");
		return 0;
	}
	if (!wizardp(this_player()) &&
		(string) this_player()->query("name") != owner &&
		(undefinedp(owners=_varlist["owners"]) ||
		 pointerp(owners) &&
		 member_array(this_player()->query("name"), owners)==-1))  {
		notify_fail("You aren't allowed to enter the storeroom\n");
		return 0;
	}
}

int shop_busy() {
	if (!shopkeeper || environment(shopkeeper)!=this_object()) {
		notify_fail("There's no one around to help you!\n");
		return 1;
	}
	if (customer) {
		if (environment(customer) != this_object()) {
			_end();
			return 0;
		}
		if (customer == this_player()) {
			notify_fail( "The shopkeeper says: "+
			   "I'm already serving you, please be patient.\n");
			return 1;
		}
		notify_fail( "The shopkeeper says: "+
		    "I'm now busy with "+customer->query("cap_name")+
			", but I'll be with you in a moment...\n");
		return 1;
	}
	return 0;
}

int list() {
	mixed tmp;

	if (shop_busy()) return 0;
	write ("You ask the shopkeeper: What do you have?\n");
	say(this_player()->query("cap_name")+
		" asks the shopkeeper: What do you have?\n");

	customer = this_player();
	if (!undefinedp(tmp = _varlist["list"]) && Is_Block(tmp) ) {
		customer = this_player();
		Push("("+this_player()->query("name")+")");
		Compile("list");
		return 1;
	}

	tell_room(this_object(),"The shopkeeper says: hmm... lemme see..\n");
	say("The shopkeeper shows the wares to "
		+this_player()->query("cap_name")+".\n");
	_show_goods();
	customer = 0;
	return 1;
}

int _say() {
        mixed i;
        object env, ob;
        string out;
        i = Pop();
        if (Is_String(i)) {
                out = Parse_String(i);
        }
        else if (Is_Object(i)) {
                ob = find_object(i[2..strlen(i)-1]);
                if (!ob) out = "something";
                else out = (string) ob->query("short");
                if (!out) out = "some weird thing";
        }
        else if (objectp(i)) {
                if (!i) out = "something";
                else out = (string) ob->query("short");
                if (!out) out = "some weird thing";
        }
        else if (intp(i)) {
                out = ""+i;
        }
        else if (pointerp(i)) {
                out = Restore_Array(i);
        }
	if (out) {
		tell_room(this_object(),
		"The shopkeeper says: "+out+"\n");
	}
        return 0;
}

int _end() {
	last_customer = customer;
	customer = 0;
	_counter = 0;
	_stack_ptr = 0;
	_pending_input = 0;
	_stack = ({ });
	remove_call_out("Compile");
}

int _show_goods() {
	int i,n;
	object *ob;
	mixed * value;
	string type;
	int number;

	/* Objects in local storage */
	if (!customer || environment(customer)!=this_object()) return 0;

	ob = all_inventory(storeroom) ;
	n = sizeof(ob);
	if (!n) tell_room(this_object(),
		"The shopkeeper says: we have nothing right now.\n");
	for (i=0;i<n;i++) {
		value = ob[i]->query("value") ;
		if (!value) continue ;
		type = value[1] ;
		number = value[0] ;
		if (number==0) continue ;
		tell_object(customer, "   "+ob[i]->query("short")+"\n");
	}
	/* this command is too costly, must make LSC to rest for 2 seconds */
	_counter = 0;
	return 0;
}

int _evaluate() {
	mixed obj;
	mixed *value;
	string type;
	int number, j;

	obj = Pop();
	if (!objectp(obj)) {
		error("evaluate: not an object");
		return -1;
	}
	value = obj->query("value");

	/* can add randomness here ... */

	type = value[1];
	number = value[0];
	Push(coinvalue(type) * number);
}

int _set_desired_buy_price() {
	mixed price;
	price = Pop();
	if (!intp(price)) {
		error("set_desired_buy_price: not an integer");
		return -1;
	}
	desired_buy_price = price;
	return 0;
}

int _set_desired_sell_price() {
	mixed price;
	price = Pop();
	if (!intp(price)) {
		error("set_desired_sell_price: not an integer");
		return -1;
	}
	desired_sell_price = price;
	return 0;
}

int _convert_amount_str() {
	mixed value;

	value = Pop();
	if (!intp(value)) {
		error("convert_amount_str: not an integer");
		return -1;
	}
	
	return Push(Convert_amount_string(value));
}

string Convert_amount_string(int value) {
	int i,n,num, mod, first;
	string str;
	mixed *types;

	str = "";
	for(i=money_type_size-1;i>=0;i--) {
		num = value / type_value[i];
		mod = value - num * type_value[i];
		value = mod;
		if (!num) continue;
		if (!value && str != "") {
			str += " and ";
			if (num==1) str+="one "+money_types[i]+" coin";
			else str+=num+" "+money_types[i]+ " coins";
			break;
		}
		if (str!="") str += ", ";
		if (num==1) str+="one "+money_types[i]+" coin";
		else str+=num+" "+money_types[i]+" coins";
	}
	return str;
}

string coins_string(int * coins) {
	int i;
	int tmp;
	tmp = 0;
	for(i=0;i<money_type_size;i++) 
		tmp+=coins[i] * type_value[i];
	return Convert_amount_string(tmp);
}

int value(string str) {
	object obj ;
	string word ;
	string *types ;
	int i, number, changenum ;
	string type, changetype ;
	mixed *value ;
	mixed tmp;
	int newval, oldval, newnum, j ;

	if (!str) {
		notify_fail("What do you want to value?\n");
		return 0;
	}
	if (shop_busy()) return 0;
	obj = present(str,this_player()) ;
	if (!obj) {
		notify_fail ("You don't have a "+str+".\n") ;
		return 0 ;
	}
	value = obj->query("value") ;
	if (!value) {
		notify_fail ("The shopkeeper says: "+
			"That object is not worth anything.\n") ;
		return 0 ;
	}
	type = value[1] ;
	number = value[0] ;
	if (number==0) {
		notify_fail ("The shopkeeper says: "+
			"That object is not worth anything.\n") ;
		return 0 ;
	}
	j = member_array(type,money_types) ;
	if (j==-1) {
		write("The shopkeeper says: I don't know how to value this.\n");
		return 1;
	}

	write("You ask the shopkeeper to value it for you...\n");
	say(this_player()->query("cap_name")+" puts forward something and"+
		" asks the shopkeeper a question.\n");

	if (!undefinedp(tmp = _varlist["value"]) && Is_Block(tmp) ) {
		item = obj;
		customer = this_player();
		Push("("+this_player()->query("name")+")");
		Push(item);
		Compile("value");
		return 1;
	}

	write("The shopkeeper says: "+obj->query("short") + " is worth "+
		Convert_amount_string(number*type_value[j]*SHOP_SALES_FRAC));

	return 1 ;
}

int _buy_price() {
	mixed * value;
	if (!desired_buy_price) {
		if (!item) {
			error("buy_price: no reference");
			return -1;
		}
		error("buy_price: don't know buy price");
		return -1;
	}
	return Push(desired_buy_price);
}

int _sell_price() {
        mixed * value;
        if (!desired_sell_price) {
                if (!item) {
                        error("sell_price: no reference");
                        return -1;
                }
                error("sell_price: don't know buy price");
                return -1;
        }
        return Push(desired_sell_price);
}

int Do_buy(object ob,int how_much,object from_whom) {
	int * coins, ncoins, cap, i;
	string tmpstr;

	if (ob->move(storeroom)!=MOVE_OK) {
		tell_room(this_object(), "The shopkeeper says: "+
			"Our storeroom can't hold your stuff, sorry\n");
		return -1;
	}
	/* calculate money to give customer */
	coins = best_coin_combination(how_much,
		wealth,
		(mapping) from_whom->query("wealth"),
		(int) storeroom->query("capacity"),
		cap = (int) from_whom->query("capacity"));
	if (!coins) {
		if (fail_reason == NOWEALTH) {
			tell_room(this_object(), "The shopkeeper says: "+
				"I'm running out of coins, sorry\n");
		}
		if (fail_reason == NOCHANGE) {
			tell_room(this_object(), "The shopkeeper says: "+
				"We don't have enough change to make "+
				"the deal fair to us\n");
		}
		ob->move(from_whom);
		return -1;
	}
	ncoins = 0;
	for(i=0;i<money_type_size;i++) {
		ncoins += coins[i];
		if (coins[i]>=0) {
			give_coins[i] = coins[i];
			get_coins[i] = 0;
		} else {
			give_coins[i] = 0;
			get_coins[i] = - coins[i];
		}
	}
	from_whom->set("capacity", cap+ncoins);
	for(i=0;i<money_type_size;i++) {
		int tmp;
		wealth[money_types[i]] -= coins[i];
		tmp = from_whom->query("wealth/"+money_types[i]);
		from_whom->set("wealth/"+money_types[i], tmp+coins[i]);
	}
	tell_object(from_whom,"You get "+coins_string(give_coins));
	if ((tmpstr=coins_string(get_coins))=="")
		tell_object(from_whom,".\n");
	else 
		tell_object(from_whom," and give "+tmpstr+" as change.\n");
	cost[ob] = how_much;
	item = 0;
	return 0;
}

int _buy_it() {
	mixed how_much;

	if (!customer || environment(customer)!=this_object()) return Push(-1);
	if (!shopkeeper || environment(shopkeeper) != this_object())
		return Push(-1);

	how_much = Pop();

	if (!item) {
		tell_object(customer,
		"The shopkeeper says: Where's the thing now?\n");
		return Push(-1);
	}

	if (environment(item) != this_object() && environment(item)!=customer ||
		environment(customer) != this_object() ) 
		return Push(-1);
	_counter = 0;
	Push(Do_buy(item, how_much, customer));
}

int sell(string str) {
	int i, j ;
	object ob ;
	mixed *value, tmp ;
	string type, tmpstr;
	int number ;

	if (shop_busy()) return 0;
	if (!str) {
		notify_fail("What do you want to sell?\n") ;
		return 0 ;
	}
	ob = present(str,this_player()) ;
	if (!ob) {
		notify_fail("You don't have one to sell!\n") ;
		return 0 ;
	}

	value = ob->query("value") ;
	if (value==0) {
		notify_fail ("The shopkeeper says: That has no value.\n") ;
		return 0 ;
	}
	number=value[0] ;
	if (number<1) {
		notify_fail ("The shopkeeper says: That has no value.\n") ;
		return 0 ;
	}
	type = value[1] ;
	j = member_array(type,money_types) ;
	if (j==-1) {
		write("The shopkeeper says: I'm not interested in this.\n");
		return 1;
	}

	write("You put "+str+" forward to the shopkeeper...\n");
	say(this_player()->query("cap_name")+" wants to sell something.\n");

	if (!undefinedp(tmp = _varlist["sell"]) && Is_Block(tmp) &&
	    !undefinedp(tmp = _varlist["myvalue"]) && Is_Block(tmp) ) {
		tmpstr = "("+this_player()->query("name")+")";
		if (last_customer == this_player() && item == ob) {
			/* the item has already been valued */
			customer = this_player();	
			Push(tmpstr);
			Push(item);
			Compile("sell");
			return 1;
		}
		customer = this_player();
		item = ob;
		Push(tmpstr);
		Push(item);
		Push(tmpstr);
		Push(item);
		Compile("myvalue pop sell");
		return 1;
	}

	Do_buy(ob, number*type_value[j]*SHOP_SALES_FRAC, this_player());
	return 1 ;
}

int _cost() {
	mixed ob;
	int i;
	mixed * value;
        string type;
        int number;

	ob = Pop();
	if (!objectp(ob)) {
		error("cost: not an object");
		return -1;
	}
	i = cost[ob];
	if (undefinedp(i) || !i) {
		value = ob->query("value");
		if (!value) return Push(0);
		type = value[0];
		number = value[1];
                i = member_array(type,money_types);
		if (i=-1) return Push(0);
		return Push( number * type_value[i]);
	}

	return Push(i);
}

int ask(string str) {
	object ob ;
	string type, *types ;
	int i, number;
	mixed *value ;
	mixed tmp;

	if (!str) {
		notify_fail("What item's price you want to ask?\n");
		return 0;
	}
	if (shop_busy()) return 0;

	ob = present(str, storeroom);
	if (!ob) {
		notify_fail("We don't have one of those for sale.\n");
		return 0;
	}
	value = ob->query("value");
	if (!value) {
		notify_fail ("That object should not have been in the shop.\n");
		ob->remove() ;
		return 0 ;
	}
	type = value[1] ;
	number = value[0] ;
	if (number==0) {
		notify_fail ("That object should not have been in the shop.\n");
		ob->remove() ;
		return 0 ;
	}
	write("You ask the shopkeeper about the price of "+str+"....\n");
	say(this_player()->query("cap_name")+
		" asks about the price of something.\n");

	if (!undefinedp(tmp = _varlist["ask"]) && Is_Block(tmp) ) {
		item = ob;
		customer = this_player();
		Push("("+this_player()->query("name")+")");
		Push(item);
		Compile("ask");
		return 1;
	}

	write("The shopkeeper says: "+ ob->query("short")+" costs "+
		Convert_amount_string(cost[ob] * 5/4)
		+"\n");

	return 1;
}

int Do_sell(object ob,int how_much,object to_whom) {
	int * coins, ncoins, cap, i;
	string tmpstr;

	if (ob->move(to_whom)!=MOVE_OK) {
		tell_room(this_object(), "The shopkeeper says: "+
			"You can't carry anymore, sorry\n");
		return -1;
	}
	/* calculate money to give customer */
	coins = best_coin_combination(how_much,
		(mapping) to_whom->query("wealth"),
		wealth,
		cap = (int) to_whom->query("capacity"),
		(int) storeroom->query("capacity"));
	if (!coins) {
		if (fail_reason == NOWEALTH) {
			tell_room(this_object(), "The shopkeeper says: "+
				"You don't have enough money!\n");
		}
		if (fail_reason == NOCHANGE) {
			tell_room(this_object(), "The shopkeeper says: "+
				"We don't have enough change to make "+
				"the deal fair to us\n");
		}
		ob->move(storeroom);
		return -1;
	}
	ncoins = 0;
	for(i=0;i<money_type_size;i++) {
		ncoins += coins[i];
		if (coins[i]>=0) {
			get_coins[i] = coins[i];
			give_coins[i] = 0;
		} else {
			get_coins[i] = 0;
			give_coins[i] = - coins[i];
		}
	}
	to_whom->set("capacity", cap-ncoins);
	for(i=0;i<money_type_size;i++) {
		int tmp;
		wealth[money_types[i]] += coins[i];
		tmp = to_whom->query("wealth/"+money_types[i]);
		to_whom->set("wealth/"+money_types[i], tmp-coins[i]);
	}
	tell_object(to_whom,"You pay "+coins_string(get_coins));
	if ((tmpstr=coins_string(give_coins))=="")
		tell_object(to_whom,".\n");
	else
		tell_object(to_whom," and get back "+tmpstr+" as change.\n");
	map_delete(cost, ob);
	item = 0;
	return 0;
}

int _sell_it() {
	mixed how_much;

	if (!customer || environment(customer)!=this_object()) return Push(-1);
	if (!shopkeeper || environment(shopkeeper) != this_object())
		return Push(-1);

	how_much = Pop();

	if (!item) {
		tell_object(customer,
		"The shopkeeper says: Oops, where is it?\n");
		return Push(-1);
	}
	if (environment(item) != storeroom) {
		tell_object(customer,
		"The shopkeeper says: We don't have it\n");
		return Push(-1);
	}
	_counter = 0;
	Push(Do_sell(item, how_much, customer));
}

int buy(string str) {
	int i, j ;
	object ob ;
	mixed *value, tmp ;
	string type, tmpstr;
	int number ;

	if (shop_busy()) return 0;
	if (!str) {
		notify_fail("What do you want to buy?\n") ;
		return 0 ;
	}
	ob = present(str,storeroom) ;
	if (!ob) {
		notify_fail("You don't see any!\n") ;
		return 0 ;
	}

	value = ob->query("value") ;
	if (value==0) {
		notify_fail("The shopkeeper says: That one is not for sale.\n");
		return 0 ;
	}
	number=value[0] ;
	if (number<1) {
		notify_fail("The shopkeeper says: That one is not for sale.\n");
		return 0 ;
	}
	type = value[1] ;
	write("You tell the shopkeeper that you want to buy "+str+"...\n");
	say(this_player()->query("cap_name")+" wants to buy something.\n");

	if (!undefinedp(tmp = _varlist["buy"]) && Is_Block(tmp) &&
	    !undefinedp(tmp = _varlist["myask"]) && Is_Block(tmp) ) {
		tmpstr = "("+this_player()->query("name")+")";
		if (last_customer == this_player() && item == ob) {
			/* the item has already been valued */
			customer = this_player();
			Push(tmpstr);
			Push(item);
			Compile("buy");
			return 1;
		}
		customer = this_player();
		item = ob;
		Push(tmpstr);
		Push(item);
		Push(tmpstr);
		Push(item);
		Compile("myask pop buy");
		return 1;
	}

	Do_sell(ob, cost[ob]*5/4, this_player());
	return 1 ;
}

int read_lsc_file() {
	int i,n, tmp_wait_time, tmp_speed;
	string file;
	string * lines;

	file = file_name(this_object());
	sscanf(file,"%s#%d",file, n);
	file += ".lsc";
	if (file_size(file) <= 0) return 0;
	lines = explode(read_file(file),"\n");
	n = sizeof(lines);
	tmp_wait_time = wait_time;
	tmp_speed = _speed;
	wait_time = 4;
	_speed = 1;
	for(i=0;i<n;i++)
		Compile(lines[i]);
	wait_time = tmp_wait_time;
	_speed = tmp_speed;
	return 1;
}

error(string str) {
	tell_room(this_object(),str+"\n");
	_status = ERROR;
}

clean_up() { return; }