import re,math,copy,gzip,cPickle as pk
from util import *
from event import *

prop = property

dirs = ["north","south","east","west","up","down"]
rdirs = ["south","north","west","east","down","up"]

class Thing(object):
    all = {}  # track of instances of this class
    names = ()
    name = ""
    c = 0 # no connection
    SE = prop(lambda S: ["it","he","she"][S.sex])
    SS = prop(lambda S: ["its","his","her"][S.sex])
    SM = prop(lambda S: ["it","him","her"][S.sex])
    desc = "Fairly ordinary."
    ldesc = "is here."
    loc = 0 # location
    sex = 0
    shop = 0
    nsave = [] # attributes that shouldn't be saved

    room = prop(lambda S: S.loc and S.loc.room)

    def __init__(S):
        Thing.all[S] = 0

    def __getstate__(S):
        d = S.__dict__.copy()
        # forget these values
        for x in S.nsave:
            if x in d: del d[x]
        return d

    def __setstate__(S,s):
        S.__dict__.update(s)
        Thing.all[S] = 0

    def travel(S,dest):
         p = S.room.route(dest,{})
         if p:
             S.path = p
             adde(1,4,tup,[S])

    def goto(S,dest):
        if S.loc: S.loc.inv.remove(S)
        S.loc = dest
        if dest: dest.inv.append(S)

    def on_get(S,by): by.sendl("You can't get that.")
    def on_drop(S,by): by.sendl("You can't drop that.")
    def on_give(S,by,to): by.sendl("You can't give that away.")
    def recv(S,x,fr): by.sendl("It's not interested.")
    def on_wear(S,by): by.sendl("You can't wear that.")
    def on_remove(S,by): by.sendl("Not wearable.")
    def on_drink(S,by): by.sendl("You can't drink that.")

    def sendl(S,t): pass  # ignore
    write = sendl         # ditto

class Container(Thing):
    maxc = 5  # max num of items this can carry
    def __init__(S):
        super(Container, S).__init__()
        S.inv = []

class Biz(object):
    owner = ""
    rent = 1
    zone = "smbiz"
    type = ""

CITY = Biz() # the city owns all public property
CITY.owner = "CITY"

class Room(Container):
    name = "Somewhere"
    biz = CITY
    room = prop(lambda S: S)

    def __init__(S):
        super(Room, S).__init__()
        S.ex = {}

    def __getstate__(S):
        d = S.__dict__.copy()
        d["inv"] = [x for x in d["inv"] if not is_a(x,Player)]
        return d

    def try_edit(S, a):
        if a.admin or a.name == S.biz.owner: return 1
        a.sendl("You can only modify property that you own or rent.")

    def descTo(S, v):
        r = ""
        if not S.biz.owner: r = " ({gFor Rent{x)"
        elif S.biz.owner != 'CITY': r = " ({yRun by %s{x)" % S.biz.owner

        d = "{B%s{x%s\r\n  %s\r\n{x" % (S.name, r, S.desc)
        if len(S.inv) > 1: d += NL

        for x in S.people+S.items:
            if x is not v: d += "%s %s\r\n" % (cap(x.name), x.ldesc)
        return d

    # Finds a path to a destination room
    # Uses a really stupid DFS that does not attempt to pick the shortest path
    # But hey, at least this is dead simple when every byte counts
    def route(S,dest,vi):
        if S in vi: return
        vi[S] = 0
        for d,r in S.ex.items():
            if r == dest: return [d]
            x = r.route(dest,vi)
            if x: return x+[d]

    crowd = prop(lambda S: [p for p in S.people if p.npc])
    people = prop(lambda S: [o for o in S.inv if is_a(o,Actor)])
    items = prop(lambda S: [o for o in S.inv if not is_a(o,Actor)])
    shops = prop(lambda S: [x.shop for x in S.inv+[S] if x.shop])

# Items are carryable objects
class Item(Thing):
    dam = 5   # weapon damage
    wloc = 0  # wear location

    # is worn?
    worn = prop(lambda S: S.loc and is_a(S.loc, Actor) and
S.loc.worn.get(S.wloc) == S)

    def on_get(S,by):
        if S.loc == by: return by.sendl("You already have that.")
        if len(by.inv) > by.maxc: return by.sendl("Your inventory is full.")
        Act(by,S).subj("You get $VN.").room("$SN gets $VN.")
        S.goto(by)

    def on_drop(S,by):
        if S.worn: return by.sendl("You're wearing that.")
        Act(by,S).subj("You drop $VN.").room("$SN drops $VN.")
        S.goto(by.room)

    def on_give(S,by,to):
        if S.worn: return by.sendl("You're wearing that.")
        if len(to.inv) > to.maxc:
            return Act(by,to).subj("$VE can't carry any more items.")
        to.recv(S,by)

    def on_throw(S,by,v):
        Act(by,S,v).subj("You throw $VN at $ON!").vict(
"$SN throws $VN at you!").room("$SN throws $VN at $ON!")
        S.on_splat(v,by)

    # default splat is more like a *BONK*
    def on_splat(v,by):
        by.hit(S.dam*1.5)
        S.goto(v.room) # fall into room

class Actor(Container):
    cash = 200
    npc = 1
    hpc = 50  # hp
    hpm = 50  # max hp
    rpc = 100 # respect
    rpm = 100 # max respect
    foe = 0   # fighting against this person
    ks = 0    # number of attacks available
    FISTS = Item() # magical default fist weapon
    FISTS.name = "a pair of fists"
    FISTS.dam = 5
    nsave = ["foe"]
    path = []

    def __init__(S):
        super(Actor, S).__init__()
        S.worn = {}

    def do(S, l): import cmd; cmd.do(S, l)

    def recv(S,x,fr):
        Act(fr,x,S).subj("You give $VN to $ON.").obj("$SN gives you $VN."
).room("$SN gives $VN to $ON.")
        x.goto(S)

    # look for something in the current room (1) or my inv (2) or both (0)
    def find(S,n,w=0):
        return find(n,[S.room.inv+S.inv,S.room.inv,S.inv][w])

    # move in a direction
    def go(S, dir):
        if S.foe: return S.sendl("You're fighting!")
        dest = S.room.ex.get(dir)
        if dest:
            Act(S).room("$SN leaves to the %s." % dir)
            S.goto(dest)
            S.do("l")
            Act(S).room("$SN arrives from the %s." % rdirs[ dirs.index(dir) ])
        else:
            S.sendl("You can't go that way.")

    def flee(S):
        S.unfight()
        if S.room.ex: S.go(choice(S.room.ex.keys()))

    def fight(S,v):
        if S.foe:
            if v and v != S.foe: S.sendl("You're already fighting someone.")
            else: return 1
        elif v == S: S.sendl("Hah, no.")
        elif not v: S.sendl("Not fighting anyone yet.")
        elif not is_a(v,Actor): S.sendl("No use fighting that.")
        elif v.foe and v.foe != S: S.sendl(v.name+" is busy.")
        else:
            Act(S,v).subj("You prepare to fight $VN.").vict(
"$SN prepares to fight you!").room("$SN glares dangerously at $VN.")
            S.foe = v
            v.foe = S
            v.ks = 0
            adde(3,2,fup,(S,))
            adde(6,2,fup,(v,))
            adde(10,5,fam,(S,v))
            return 1

    def unfight(S):
        if S.foe: S.foe.foe = 0
        S.foe = 0

    def buy(S,s,z):
        x,q,pr = z
        if q < 1: S.sendl("Out of stock.")
        elif pr > S.cash: S.sendl("Can't afford it.")
        else:
            y = copy.copy(x)
            y.goto(S)
            Act(S,y).subj("You buy $VN.").room("$SN buys $VN.")
            S.cash -= pr
            z[1] -= 1  # quantity in-stock decreases
            s.rev += pr
            return 1

    status = lambda S: "{W%s{x appears %s and %s." % (S.name,scale(rscale,S.rpc),
scale(hscale,100*S.hpc/S.hpm))

    # checks the chance that the *attacker* hits
    # warning, returns a float
    def checkhit(S,bdam,chance=80):
        # If hurt, there's a penalty (gradually increasing up to 15%)
        chance -= 30*((1.5**(1-1.*S.hpc/S.hpm))-1)

        if randint(0,100) < chance: return gauss(bdam,bdam/4.)
        return 0

    def checklose(S):
        import world
        if S.hpc > 0 and S.rpc > 0: return

        if S.hpc < 1: # out of HP
            Act(S).subj("{YYou {Rcollapse{Y and black out.{x").room(
"{Y$SN collapses and is carried away by paramedics.{x")

        elif S.rpc < 1: # out of RP
            Act(S).subj("{YUtterly disgraced, you run home.{x").room(
"{Y$SN runs away in total disgrace!{x")

        S.goto(world.start)
        S.hpc = S.hpm; S.rpc = S.rpm
        S.path = 0

    def hit(S,dam): # physical attack
        dam = int(S.checkhit(dam))

        S.ks -= 1 # one less attack

        # People don't like it if you use violence against your opponent
        # But it matters a lot less if you don't have any respect left anyway
        rloss = S.pmult(dam/2.+max(100*(1.12**(1.*S.rpc/S.rpm)-1),3))

        foe = S.foe
        S.sendl("{BYou lost %d respect for your actions, but %s lost %d health.{x"
% (rloss,foe.name,dam))
        foe.sendl("{RYou lost %d health from the attack, but %s lost %d respect.{x"
% (dam,S.name,rloss))

        S.rpc -= rloss    # violence penalty
        foe.hpc -= dam       # ouch
        S.sendl(foe.status())
        foe.checklose(); S.checklose() # yes, both sides can be eliminated

    def pmult(S,dam): # modify damage for number of people watching
        y = len(S.room.crowd)
        return int(round(dam * math.log(max(y,1)+1)))

    def rhit(S,dam,t): # dissin'
        foe = S.foe

        dam = S.checkhit(dam)

        S.ks -= 1  # one less attack

        if not dam: # failure
            Act(S,t).subj("{BYour $VT didn't have quite the devastating finesse you wanted.{x").room(
"$SN's poorly executed $VT fell flat.")
            return

        # factor in number of people watching
        dam = S.pmult(dam)

        S.sendl("{BYour %s causes %s to lose %s respect.{x" % (t,S.foe.name,dam))
        foe.sendl("{RYou lose %d respect to that %s!{x" % (dam,t))

        foe.rpc -= dam
        S.sendl(foe.status())

        foe.checklose()


class Shop(object):
    rev = 0   # revenue
    def __init__(S):
        S.inv = []

    def show(S,v):
        v.sendl("{BShop:{x")
        for x,q,pr in S.inv:
            v.sendl("  {g${x%-7.2f %-30s  {B[{x%3d in stock{B]{x" % (pr,x.name,q))

    def find(S, n):
        return find(n,S.inv,lambda x:x[0])

    # Restocks a shop, naively.
    # You should add support for player-settable quantities and varying
    #  supply costs.
    # Returns how much the restocking costed
    def restock(S):
        e = 0
        for z in S.inv:
            n = 10
            z[1] += n  # increase item stock by a fixed amount
            e += n/2   # fixed cost of $0.50 to restock each item
        return e

class BevShop(Shop):
    def __init__(S,type):
        super(BevShop,S).__init__()
        S.type = type      # type, e.g. lemonade, coffee, etc
        S.fls = ['plain']  # flavors

class Sample(Item): # sample of a flavor, e.g. a cherry or a chocolate bar
    fl = "" # flavor

# Clothes make you look nice and respectable
# Although there's no penalty for mismatched outfits ... yet

class Clothing(Item):
    rpm = 0
    def on_wear(S,by):
        old = by.worn.get(S.wloc)
        if old: old.on_remove(by)
        Act(by,S).subj("You wear $VN.").room("$SN wears $VN.")
        by.worn[S.wloc] = S
        by.rpm += S.rpm; by.rpc += S.rpm  # makes you look nicer

    def on_remove(S,by):
        if by.worn.get(S.wloc) != S: return by.sendl("Not wearing that.")
        Act(by,S).subj("You take off $VN.").room("$SN takes off $VN.")
        del by.worn[S.wloc]
        by.rpm -= S.rpm; by.rpc -= S.rpm
        by.checklose()

class Player(Actor):
    nsave = ["foe","c","loc"]
    npc = 0
    admin = 0
    pro = "$nl<$hpc{D/{x${hpm}{Dhp{x $rpc{D/{x${rpm}{Drp{x {g$${x$cash> "
    def __init__(S, n):
        super(Player, S).__init__()
        S.name = n
        S.names = n.lower(),

    def sendl(S, t=""): S.c.sendl(t)
    def write(S, t): S.c.write(t)

    def save(S):
        f = file("players/"+S.name,"w")
        pk.dump(S,f)

def pload(n): # load a player
    try:
        f = file("players/"+n)
        return pk.load(f)
    except: pass

def wsave(): # save world
    import world
    f = gzip.open("data.gz","w")
    pk.dump(world.start,f,-1)

def wload(): # load world
    import world
    f = gzip.open("data.gz")
    return pk.load(f)