#!/usr/bin/env python
import dbus, dbus.service
import gobject
from dbus.mainloop.glib import DBusGMainLoop
from dbus.service import FallbackObject as DBusFBObject
from random import randint
from math import ceil
import os, pickle
import time
import signal
import sys

DBUS_PATH_SERVER='/org/shr/Geekogotchi/Server'
DBUS_IF_SERVER='org.shr.Geekogotchi.Server'
DBUS_PATH_PETS='/org/shr/Geekogotchi/Pets'
DBUS_PATH_PET='/org/shr/Geekogotchi/Pet'
DBUS_IF_PET='org.shr.Geekogotchi.Pet'
DBUS_IF_PETS='org.shr.Geekogotchi.Pets'

PERSIST_DATA='/usr/share/geekogotchi/pets.pickle'

SENDERS = ['Amy', 'Mary', 'Allison', 'Joanne']
NAMES = [
    ['Tuxi'],
    ['Tux']
        ]

if '--debug' in sys.argv or '-d' in sys.argv:
  UPDATE_INTERVAL = 1
  DAY_LENGTH = 2
else:
  UPDATE_INTERVAL = 60
  DAY_LENGTH = 60*60*24

VERSION = '0.0.0.4'

class Server(DBusFBObject):

  def __init__(self):
    DBusFBObject.__init__( self, conn=bus, object_path=DBUS_PATH_SERVER )
    self.pets = Pets()

  def start(self):
    self.pets.start()
    print 'Server started'

  def stop(self):
    self.pets.store()

  def update(self):
    self.pets.update()
    return True

  @dbus.service.method(DBUS_IF_SERVER, "", "s")
  def GetVersion(self):
    return VERSION

class Pets(DBusFBObject):

  pets = None

  def __init__(self):
    DBusFBObject.__init__( self, conn=bus, object_path=DBUS_PATH_PETS )
    self.pets = {}
    self.petcount = -1
    if os.path.exists(PERSIST_DATA):
      pickleFile = open(PERSIST_DATA, "r")
      self.petcount, petprops = pickle.load(pickleFile)
      for pet in petprops:
        self.pets[pet]= Pet(pet, petprops[pet])

  def start(self):
    for pet in self.pets:
      if not self.pets[pet].props['Dead']:
        gobject.timeout_add_seconds(UPDATE_INTERVAL, self.pets[pet].update)
    bus.add_signal_receiver(self.resume, dbus_interface="org.freesmartphone.Usage", signal_name="SystemAction")

  def resume(self, res):
    if res=='RESUME':
      print "Resume!"
      self.update()

  def update(self):
    for pet in self.pets:
      if not self.pets[pet].props['Dead']:
        self.pets[pet].update()

  def store(self):
    print "storing"
    pickleFile = open(PERSIST_DATA, "w")
    petprops = {}
    for pet in self.pets:
      petprops[pet] = (self.pets[pet].props, self.pets[pet].privprops)
    pickle.dump((self.petcount, petprops),pickleFile)
    pickleFile.close()

  @dbus.service.method(DBUS_IF_PETS, "", "i")
  def AddPet(self):
    self.petcount += 1
    self.pets[self.petcount] = Pet(self.petcount)
    self.pets[self.petcount].update()
    gobject.timeout_add_seconds(UPDATE_INTERVAL, self.pets[self.petcount].update)
    return self.petcount

  @dbus.service.method(DBUS_IF_PETS, "i", "")
  def KillPet(self, id):
    self.pets[id].kill()

  @dbus.service.method(DBUS_IF_PETS, "i", "")
  def DeletePet(self, id):
    del self.pets[id]

  @dbus.service.method(DBUS_IF_PETS, "", "ai")
  def GetPets(self):
    pets = []
    for pet in self.pets:
      pets.append(pet)
    return pets

  @dbus.service.method(DBUS_IF_PETS, "", "ai")
  def CleanPets(self):
    pets = []
    for pet in self.pets:
      if self.pets[pet].props['Dead']:
        pets.append(pet)
        del self.pets[pet]
    return pets

class Pet(DBusFBObject):

  id = None
  pets = None
  props = None

  def __init__(self, id, props = None):
    self.id = id
    DBusFBObject.__init__( self, conn=bus, object_path=DBUS_PATH_PET+'/'+str(self.id) )
    if not props:
      self.props = {'Alive': True, 'Age':-1, 'Dead': False, 'Borned':False}
      self.privprops = {}
      self.privprops['MaxHHH']=randint(900, 1200)
      self.privprops['MaxAge']=randint(60, 110)
      self.privprops['HHHStep']=randint(1,2)
      self.privprops['DeadLimit']=randint(-15, -5)

      self.privprops['LastUpdate']=time.time()
      self.privprops['Created']=time.time()

      self.privprops['Happiness']=self.privprops['MaxHHH']
      self.privprops['Hungriness']=0
      self.privprops['Health']=self.privprops['MaxHHH']
    else:
      self.props = props[0]
      self.privprops = props[1]
      self.update()

  def send_message(self, who, text):
    print who+': '+text
    try:
      self.messagesproxy = dbus.Interface(bus.get_object('org.freesmartphone.opimd', '/org/freesmartphone/PIM/Messages'), dbus_interface='org.freesmartphone.PIM.Messages')
      self.messagesproxy.AddIncoming({'Direction':'in', 'Source':'Geekogotchi', 'Sender': who, 'Content':text})
    except:
      print "Unable to send message to opimd"

  def calcPerc(self, val, max, upd = None):
    perc = (100*val)/max
    if perc < 0:
      if upd=='Hungriness':
        self.privprops[upd] = 0
      return 0
    elif perc > 100:
      if upd and upd!='Hungriness':
        self.privprops[upd] = self.privprops['MaxHHH']
      return 100
    else:
      return perc

  def calculateHHH(self):
    try:
      self.props['Happiness']=self.calcPerc(self.privprops['Happiness'],self.privprops['MaxHHH'], 'Happiness')
      self.props['Hungriness']=self.calcPerc(self.privprops['Hungriness'],self.privprops['MaxHHH'], 'Hungriness')
      self.props['Health']=self.calcPerc(self.privprops['Health'],self.privprops['MaxHHH'], 'Health')
    except KeyError:
      pass

  def update(self):
    print "update " + str(self.id)
    diff = time.time()-self.privprops['LastUpdate']
    if self.props['Alive']:
      if diff>(UPDATE_INTERVAL):
        num = int(ceil(diff))/UPDATE_INTERVAL
        for i in range(1, num-1):
          self.calculate()
          if not self.props['Alive']:
            break
      return self.calculate()

  def calculate(self):
    print "calculate " + str(self.id)
    self.privprops['LastUpdate']=time.time()
    if self.props['Alive']:
      oldage = self.props['Age']
      self.props['Age'] = int(ceil(((ceil(time.time())-ceil(self.privprops['Created']))/DAY_LENGTH)-1))
      if self.props['Age'] > oldage and self.props['Borned']:
        self.Birthday(self.props['Age'])
      if not self.props['Borned'] and self.props['Age']==0:
        self.born()
      step = self.privprops['HHHStep']
      if self.props['Borned']:
        self.privprops['Happiness'] -= 3*step
        self.privprops['Hungriness'] += 2*step
        self.privprops['Health'] -= step
      self.calculateHHH()
      if self.check_for_death():
        self.StateUpdated(self.props)
        return False
      self.StateUpdated(self.props)

    return True

  def check_for_death(self):
    if self.props['Dead']:
      return False
    if self.props['Age']>=self.privprops['MaxAge']:
      self.kill('age')
      return True
    if self.privprops['Happiness']<self.privprops['DeadLimit']:
      self.kill('boredom')
      return True
    if self.privprops['Health']<self.privprops['DeadLimit']:
      self.kill('illness')
      return True
    if self.privprops['Hungriness']>self.privprops['MaxHHH']-self.privprops['DeadLimit']:
      self.kill('hungry')
      return True
    return False

  def born(self):
    self.props['Borned']=True
    sex = randint(0,1)
    if sex:
      self.props['Gender']='male'
    else:
      self.props['Gender']='female'
    self.privprops['Happiness']=(randint(20, 40)*self.privprops['MaxHHH'])/100
    self.privprops['Hungriness']=(randint(0, 10)*self.privprops['MaxHHH'])/100
    self.privprops['Health']=(randint(90, 100)*self.privprops['MaxHHH'])/100
    self.calculateHHH()
    self.PetBorned()
    self.StateUpdated(self.props)

  def kill(self, reason = 'killed'):
    if self.props['Borned']:
      del self.props['Happiness']
      del self.props['Hungriness']
      del self.props['Health']
    self.props['Alive'] = False
    self.props['Dead'] = True
    self.props['DeadReason'] = reason
    self.StateUpdated(self.props)
    self.PetDeath(reason)

  def check_alive(self):
    if not self.props['Alive']:
#      raise(PetDead)
      pass

  @dbus.service.signal(DBUS_IF_PET, "a{sv}")
  def StateUpdated(self, props):
    self.check_for_death()

  @dbus.service.signal(DBUS_IF_PET, "")
  def PetBorned(self):
    if self.props['Gender']=='male':
      msg = "Ooohh, that's your new pet? He is soooo cute! How will you name him? Maybe %s? That's wonderful name :)" % NAMES[1][randint(0, len(NAMES[1])-1)]
    else:
      msg = "Ooohh, that's your new pet? She is soooo cute! How will you name her? Maybe %s? That's wonderful name :)" % NAMES[0][randint(0, len(NAMES[0])-1)]
    self.send_message(SENDERS[randint(0,len(SENDERS)-1)], msg)

  @dbus.service.signal(DBUS_IF_PET, "s")
  def PetDeath(self, cause):
    pass

  @dbus.service.signal(DBUS_IF_PET, "i")
  def Birthday(self, age):
    pass

  @dbus.service.method(DBUS_IF_PET, "i", "b")
  def Feed(self, type):
    self.check_alive()
    self.privprops['Hungriness'] -= randint(90, 110)
    if self.privprops['Hungriness']<0:
      self.privprops['Happiness'] -= randint(self.privprops['Hungriness']*(-1), self.privprops['Hungriness']*(-2))
    self.calculateHHH()
    self.StateUpdated(self.props)
    return True # TODO

  @dbus.service.method(DBUS_IF_PET, "", "")
  def Heal(self):
    self.check_alive()
    self.privprops['Health'] += randint(150, 220)
    self.privprops['Happiness'] -= randint(150, 170)
    self.calculateHHH()
    self.StateUpdated(self.props)

  @dbus.service.method(DBUS_IF_PET, "i", "b")
  def Play(self, type):
    self.check_alive()
    return True # TODO

  @dbus.service.method(DBUS_IF_PET, "i", "")
  def GoodPlay(self, type):
    self.check_alive()
    self.privprops['Happiness'] += randint(90, 120)
    self.privprops['Hungriness'] += randint(20, 30)
    self.calculateHHH()
    self.StateUpdated(self.props)

  @dbus.service.method(DBUS_IF_PET, "i", "")
  def BadPlay(self, type):
    self.check_alive()
    self.privprops['Happiness'] -= randint(5, 15)
    self.privprops['Hungriness'] += randint(22, 30)
    self.calculateHHH()
    self.StateUpdated(self.props)

  @dbus.service.method(DBUS_IF_PET, "", "a{sv}")
  def GetState(self):
    return self.props

  @dbus.service.method(DBUS_IF_PET, "", "s")
  def GetName(self):
    return self.props.get('Name')

  @dbus.service.method(DBUS_IF_PET, "s", "")
  def SetName(self, name):
    self.props['Name'] = name

DBusGMainLoop(set_as_default=True)
mainloop = gobject.MainLoop()
bus = dbus.SystemBus()
try:
    busname = dbus.service.BusName( 'org.shr.Geekogotchi', bus )
except dbus.DBusException:
    print( "Can't claim dbus bus name, check configuration in /etc/dbus-1/system.d/geekogotchi.conf" )
    exit(1)

server = Server()
server.start()
signal.signal(signal.SIGTERM, server.stop)
try:
  mainloop.run()
except KeyboardInterrupt:
  server.stop()
  mainloop.quit()
