# ./addons/eventscripts/_libs/python/installlib.py

import cPickle
import es
import os
import path
import zipfile

import psyco
psyco.full()

###

class InstallInfo(object):
   version = ''

   def __init__(self, basename):
      self.basename = basename
      self.files    = set()
      self.dirs     = set()

   def getVersion(self):
      return self.version

   def setVersion(self, version):
      self.version = version

   def addFile(self, file):
      self.files.add(file)

   def getFiles(self):
      return self.files.copy()

   def addDir(self, dir):
      self.dirs.add(dir)

   def getDirs(self):
      return self.dirs.copy()


class InstallInfoManager(object):
   infopath = es.getAddonPath('_libs') + '/python/installinfo/'

   def __init__(self):
      if not os.path.isdir(self.infopath):
         os.mkdir(self.infopath)

   def getInfoPath(self, basename):
      return self.infopath + basename + '.db'

   def hasInstallInfo(self, basename):
      return os.path.isfile(self.getInfoPath(basename))

   def getInstallInfo(self, basename):
      if not self.hasInstallInfo(basename):
         return InstallInfo(basename)

      infofile = open(self.getInfoPath(basename))
      info     = cPickle.load(infofile)
      infofile.close()

      return info

   def saveInstallInfo(self, basename, info):
      infofile = open(self.getInfoPath(basename), 'w')
      cPickle.dump(info, infofile)
      infofile.close()

   def getInstalled(self):
      return [addon[addon.replace('\\', '/').rfind('/') + 1:~2] for addon in filter(lambda x: x.endswith('.db'), os.listdir(self.infopath))]

infomanager = InstallInfoManager()

###

class Addon(object):
   infomanager = infomanager
   addon_file  = None

   def __init__(self, basename, addon_file):
      self.basename   = basename
      self.addon_file = addon_file

      self.approved       = '1'
      self.currentversion = ''
      self.es_load        = basename

   def download(self):
      return self.addon_file

###

class AddonInstaller(object):
   STATUS_SUCCESSFUL      = 0
   STATUS_NO_DOWNLOAD     = 1
   STATUS_NO_INSTALL_INFO = 2
   STATUS_ALREADY_DONE    = 4
   STATUS_NOT_APPROVED    = 8

   gamepath     = str(es.ServerVar('eventscripts_gamedir')) + '/'
   autoloadpath = es.getAddonPath('_autoload') + '/'

   def __init__(self, addon):
      self.addon = addon

      self.addondir = es.getAddonPath(self.addon.es_load) + '/'

   """ """

   def isInstalled(self):
      filename = self.addon.es_load.split('/')[~0]
      return bool(filter(os.path.isfile, (self.addondir + filename + '.py', self.addondir + 'es_' + filename + '.txt'))) or self.addon.infomanager.hasInstallInfo(self.addon.basename)

   def isAutoLoaded(self):
      return os.path.isfile(self.autoloadpath + self.addon.basename + '.cfg')

   def setAutoload(self, state):
      path = self.autoloadpath + self.addon.basename + '.cfg'

      if state:
         autoloadfile = open(path, 'w')
         autoloadfile.write('es_xload ' + self.addon.es_load + '\n')
         autoloadfile.close()

      elif os.path.isfile(path):
         os.remove(path)

   """ File manipulation functions """

   def install(self, autoload=False, addon_file=None):
      if self.isInstalled(): return self.STATUS_ALREADY_DONE

      if self.addon.approved <> '1': return self.STATUS_NOT_APPROVED

      addon_file = self.addon.download() if addon_file is None else addon_file
      if not addon_file: return self.STATUS_NO_DOWNLOAD

      self._extract(addon_file, InstallInfo(self.addon.basename))
      self.setAutoload(autoload)

      return self.STATUS_SUCCESSFUL

   def update(self, force=False):
      if not self.addon.infomanager.hasInstallInfo(self.addon.basename): return self.STATUS_NO_INSTALL_INFO

      if self.addon.approved <> '1': return self.STATUS_NOT_APPROVED

      installinfo = self.addon.infomanager.getInstallInfo(self.addon.basename)
      if not force and installinfo.getVersion() == self.addon.currentversion and installinfo.getVersion(): return self.STATUS_ALREADY_DONE

      autoload   = self.isAutoLoaded()
      addon_file = self.addon.download()
      if not addon_file: return self.STATUS_NO_DOWNLOAD

      status = self.uninstall(installinfo)
      if status <> self.STATUS_SUCCESSFUL: return status

      return self.install(autoload, addon_file)

   def uninstall(self, installinfo=None):
      if installinfo is None:
         if not self.addon.infomanager.hasInstallInfo(self.addon.basename): return self.STATUS_NO_INSTALL_INFO
         installinfo = self.addon.infomanager.getInstallInfo(self.addon.basename)

      if self.addon.es_load.replace('/', '.') in self._getAddonSet(): es.unload(self.addon.es_load)

      for filename in filter(os.path.isfile, installinfo.getFiles()):
         os.remove(filename)

      for dir in filter(os.path.isdir, sorted(installinfo.getDirs(), reverse=True)):
         if not os.listdir(dir):
            os.rmdir(dir)

      for path in filter(os.path.isfile, (self.autoloadpath + self.addon.basename + '.cfg', self.addon.infomanager.getInfoPath(self.addon.basename))):
         os.remove(path)

      return self.STATUS_SUCCESSFUL

   """ Internal functions """

   def _extract(self, addon_file, installinfo):
      addon_zip = zipfile.ZipFile(addon_file)
      ziplist   = sorted(addon_zip.namelist())

      for dir in filter(lambda x: x.endswith('/'), ziplist):
         installinfo.addDir(self.gamepath + dir)

         if not os.path.isdir(self.gamepath + dir):
            os.mkdir(self.gamepath + dir)

      init_path = es.getAddonPath(self.addon.es_load) + '/__init__.py'
      installinfo.addFile(init_path)
      installinfo.addFile(init_path + 'c')

      for filename in filter(lambda x: not x.endswith('/'), ziplist):
         installinfo.addFile(self.gamepath + filename)
         if filename.endswith('.py'):
            installinfo.addFile(self.gamepath + filename + 'c')

         newfile = path.path(self.gamepath + filename)
         newfile.write_text(addon_zip.read(filename))

      installinfo.setVersion(self.addon.currentversion)
      self.addon.infomanager.saveInstallInfo(self.addon.basename, installinfo)

      addon_zip.close()

   def _getAddonSet(self):
      addon_list = set(es.createscriptlist())

      for addon in es.addons.getAddonList():
         addon_list.add(addon.__name__[:addon.__name__.rfind('.')].replace('.', '/'))

      return addon_list


import esamlib # Don't move this

def getInstaller(basename, addon_file=None, log=esamlib.dbglog):
   addon = Addon(basename, addon_file) if addon_file else esamlib.getAddon(basename, log)
   return None if addon is None else AddonInstaller(addon)

def getInstallerFromPath(basename, path, log=esamlib.dbglog):
   return getInstaller(basename, open(path ,'rb'), log)

###

def _executeAutoLoad():
   autoloadpath = es.getAddonPath('_autoload') + '/'

   if not os.path.isdir(autoloadpath):
      os.mkdir(autoloadpath)

   for filename in filter(lambda x: x.endswith('.cfg') and os.path.isfile(autoloadpath + x), os.listdir(autoloadpath)):
      es.server.queuecmd('es_xmexec ../addons/eventscripts/_autoload/' + filename)

"""
>>> import installlib

>>> randmaps = installlib.getInstaller('randmaps')
>>> randmaps.install(True) # Install RandMaps with it autoloaded
0 # Install successful

>>> randmaps.update()
4 # Already up to date
>>> randmaps.update(True) # Force update
0 # Update successful

>>> randmaps.uninstall()
0 # Uninstall successful
"""

"""
import es
import installlib

basename = 'iptocountry' # Change to the name of your addon

# Designate a file as part of your addon, to be cleaned up when updated or uninstalled
installinfo = installlib.infomanager.getInstallInfo(basename)
installinfo.addFile(es.getAddonPath('_libs') + '/python/iptocountry.db') # This file is generated by IPToCountry
installlib.infomanager.saveInstallInfo(basename, installinfo)
"""