"""
This is my aim implementation.  It requires the toc.py from
http://jamwt.com/Py-TOC/ to work.

example:

  #imstart foo bar a
  #im myfriendjoe hey dude!  i'm on aim!
  #imhist
  #imstop

  #im ## this text gets sent in reverse because it's funny.

Currently implemented:
  - the ability to start and stop an AIM session
  - the ability to talk to other folks 
  - the ability to view your AIM history during that session
  - fixed buddy notification so it works correctly now
  - conversion of html character entities for <. >, and & to regular
    characters
  - lets you write backwards

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Copyright 2002, 2003, 2004 Will Guaraldi
"""

__author__ = "Will Guaraldi <willg@bluesock.org>"
__version__ = "1.6 (15 January, 2004)"
__description__ = "AIM client for Lyntin."

import os, time
from lyntin import config, exported, event, utils
from lyntin.modules import modutils
from toc import TocTalk, BotManager


mybot = None
mybotmanager = None
mysession = None

myhistory = []
commands_dict = {}

AIM = "AIM:"

class MyBot(TocTalk):
  def __init__(self,nick,passwd):
    TocTalk.__init__(self, nick, passwd)
    self._status = {}

  def loghistory(self, screenname, message):
    # logs this to the history file for that user
    screenname = screenname.replace(" ", "").lower()
    screenname = screenname.replace("/", "")
    screenname = screenname.replace("\\", "")
    screenname = screenname.replace(":", "")
    filename = "%swbgaim/%s" % (config.options["datadir"], screenname)
    f = open(filename, "a")
    f.write(time.asctime() + "\n" + message.replace("\r", "") + "\n")
    f.close()
    
  def on_IM_IN(self, data):
    # whenever someone IMs us

    global myhistory
    # data contains "screenname:flag:message", such as
    # "jamwt:F:hey, ben.. how's work?"
    data_components = data.split(":",2)

    screenname = data_components[0]
    message = data_components[2]

    # TocTalk also includes a special helper function called
    # strip_html().  Many AIM clients like Windows AIM use HTML
    # code.  strip_html() will remove HTML tags and make it text
    message = self.strip_html(message.replace("<br>", "\n"))

    # html character entity conversion
    message = message.replace("&gt;", ">").replace("&lt", "<").replace("&amp;", "&")

    # remove ESC characters because they suck
    message = message.replace("\33", "")

    # first i wrap the text to the screen width
    message = utils.wrap_text("[" + screenname + " said]: "+ message, 70, 5, 0)

    # add some random stuff for screen display
    outmessage = AIM + " %s\n" % message

    # replace \n with \r\n
    outmessage = outmessage.replace("\n", "\r\n")

    # and toss it in as a mud event
    writedata(outmessage)
    self.loghistory(screenname, outmessage)
    myhistory.append("%s" % message)

  def on_NICK(self, data):
    writedata(AIM + " NICK: " + data)

  def on_CONFIG(self, data):
    buddies = []
    buddies.append(self.normalize(self._nick))
    for mem in data.split("\n"):
      if mem and mem[0] == "b":
        if mem[-1] == "\n":
          mem = mem[:-1]
        buddies.append(self.normalize(mem)[1:])
     
    self.flap_to_toc(2,"toc_set_config \"%s\"" % data)
    self.flap_to_toc(2,"toc_add_buddy %s" % " ".join(buddies))
    writedata(AIM + " CONFIG: Got a config update and updated %d buddies." % 
              len(buddies))

  def on_BUDDY(self, data):
    writedata(AIM + " BUDDY: " + repr(data))

  def on_UPDATE_BUDDY(self, data):
    """
    When we get an UPDATE_BUDDY event, we parse it out here and adjust
    our buddy status hashmap accordingly to reflect who's on and who's
    idle.  This information can be seen with the #imwho command.
    """
    data = data.split(":")

    user = data[0].strip()
    online = data[1]
    idle = data[4]

    if online == "T":
      if not self._status.has_key(user):
        writedata(AIM + " UPDATE_BUDDY: %s signed on." % user)
        myhistory.append(AIM + "UPDATE_BUDDY: %s signed on." % user)
      self._status[data[0].strip()] = idle
    else:
      if self._status.has_key(user):
        writedata(AIM + " UPDATE_BUDDY: %s signed off." % user)
        myhistory.append(AIM + "UPDATE_BUDDY: %s signed off." % user)
        del self._status[user]

  def on_INFO(self, data):
    writedata(AIM + " INFO:: " + repr(data))

  def on_ERROR(self, data):
    writedata(AIM + " ERROR:: " + repr(data))


def writedata(text):
  """
  Just a little helper to print messages to the user interface for
  our AIM stuff.  It does it through a MudEvent so that you can
  trigger actions from AIM events.
  """
  global mysession
  if mysession:
    text = text.replace("\n", "\r\n") + "\r\n"
    session = exported.get_session(mysession)
    if not session:
      exported.write_error("AIM: Session '%s' doesn't exist.  Switching to common session." % mysession)
      session = exported.get_session("common")
      mysession = "common"
    event.MudEvent(session, text).enqueue()
  else:
    print text


def writeim_cmd(session, args, input):
  """
  Writes a message to your AIM connection to the person specified.

  If message starts with a ##, then we reverse the message.  This
  is what I like to call a parlour trick.  People will think it's 
  amazing that you can type backwards so quickly.

  example:
    #im myfriend ## this is backwards!

  category: wbgaim
  """
  global myhistory, mybot
  if mybot:
    screennames = args["name"]
    message = args["input"]

    message = message.replace("\\;", ";").replace("\\$", "$").replace("\\%", "%")

    # this is here so that we can quickly write things backwards.
    # I find it amusing.
    if message[0:2] == "##":
      message = list(message[2:])
      message.reverse()
      message = "".join(message)

    screennames_list = screennames.split(",")

    text = utils.wrap_text("[You said to %s]: %s" % 
                           (screennames, message), 70, 5, 0)
    writedata(AIM + " %s\n" % text)

    for mem in screennames_list:
      mybot.do_SEND_IM( mem, message )
      mybot.loghistory(mem, utils.wrap_text("[You said to %s]: %s" % (mem, message), 70, 5, 0) )

    myhistory.append("%s" % text)

  else:
    exported.write_error("AIM: not connected.")

commands_dict["im"] = (writeim_cmd, "name input", "noparsing limitparsing=1")

def getwho_cmd(session, args, input):
  """
  Tells you who is online and who is idle.

  category: wbgaim
  """
  global mybot
  if mybot:
    status = mybot._status
    data = []
    keys = status.keys()
    keys.sort()
    for mem in keys:
      if status[mem] == "0":
        data.append("   %s - online" % (mem))
      else:
        data.append("   %s - %s minutes idle." % (mem, status[mem]))

    writedata("AIM status:\n%s\n" % "\n".join(data))
  else:
    exported.write_error("AIM: not connected.")

commands_dict["imwho"] = (getwho_cmd, "")


def config_cmd(session, args, input):
  """
  Retrieves a buddy list from a file and sends it to the TOC server.
  This is sometimes needed to kick-start the config/buddy notification
  stuff.

  category: wbgaim
  """
  global mybot
  if mybot:
    filename = args["filename"]

    if os.sep not in filename and not filename.startswith("http://"):
      filename = config.options["datadir"] + filename

    lines = []
    try:
      f = open(filename, "r")
      lines = f.readlines()
      f.close()
    except:
      exported.write_error("AIM: file %s cannot be opened." % filename)
      return

    mybot.on_CONFIG("".join(lines))

  else:
    exported.write_error("AIM: not connected.")

commands_dict["imconfig"] = (config_cmd, "filename")


def gethistory_cmd(session, args, input):
  """
  Retrieves history for this aim session.  All the things you've
  said and the things other people have said are in the history.
  It keeps the full history for this session.  It does not persist
  the history to a file, nor does it truncate the history during
  the session.

  category: wbgaim
  """
  global myhistory, mybot
  if len(myhistory) > 0 or mybot:
    count = args["count"]
    count = 0 - count

    writedata("AIM history:\n%s\n" % "\n".join(myhistory[count:]))
  else:
    exported.write_error("AIM: not connected.")

commands_dict["imhist"] = (gethistory_cmd, "count:int=30")


def session_cmd(ses, args, input):
  """
  An AIM session can be tacked on to an existing session.  This
  allows incoming AIM text to trigger actions as well as work
  with aliases and such.

  The #imsession command allows you to set the session.

  category: wbgaim
  """
  global mysession

  sessname = args["session"]

  mysession = exported.get_session(sessname)
  if not mysession:
    exported.write_error("Session %s does not exist." % sessname)
    return
  mysession = sessname
  exported.write_message("AIM connected with session %s." % sessname)

commands_dict["imsession"] = (session_cmd, "session")

def startup_cmd(ses, args, input):
  """
  Starts up the AIM session.  Requires a name and a password to
  connect.

  In addition, you can bind the AIM connection to a Lyntin session
  which will allow that Lyntin session to handle the incoming
  AIM messages and trigger things based upon it.

  For instance, you could have a friend pop on AIM and tell you
  something which would trigger you to kick them on the mud.

  category: wbgaim
  """
  global mybot, mybotmanager, myhistory, mysession

  name = args["name"]
  password = args["password"]
  sessname = args["session"]

  if mybot or mybotmanager:
    exported.write_error("You're probably already connected....")
    return

  mysession = exported.get_session(sessname)
  if not mysession:
    exported.write_error("Session %s does not exist." % sessname)
    return
  mysession = sessname

  writedata(AIM + " startup.\n")
  mybot = MyBot(name, password)
  mybotmanager = BotManager()
  mybotmanager.addBot(mybot, "mybot")

commands_dict["imstart"] = (startup_cmd, "name password session=common")


def shutdown_cmd(session, args, input):
  """
  Shuts down the AIM connection (theoretically).

  category: wbgaim
  """
  global mybot, mybotmanager, myhistory, mysession

  if mybotmanager:
    mybotmanager.botStop("mybot")
    writedata(AIM + " shutdown.\n")
    mybot = None
    mybotmanager = None
  else:
    writedata(AIM + " is not started.\n")

commands_dict["imstop"] = (shutdown_cmd, "")


def load():
  modutils.load_commands(commands_dict)

def unload():
  global mybotmanager, mybot
  modutils.unload_commands(commands_dict.keys())
  if mybot or mybotmanager:
    shutdown_cmd(None, None, None)