# $Id: configuration.py 23037 2014-02-17 23:25:58Z wahwah $
# coding=utf-8

import ConfigParser
import errno
import logging
import os
import re
import sqlobject

WS_RE = re.compile(r"\s+")

CHECKPKG_CONFIG_FILENAME = "checkpkg.ini"
CHECKPKG_RELMGR_CONFIG_FILENAME = "checkpkg_relmgr.ini"
CHECKPKG_AUTO_CONFIG_FILENAME = "checkpkg_auto.ini"
CHECKPKG_DEFAULTS_FILENAME = "checkpkg_defaults.ini"
CHECKPKG_DIR = "%(HOME)s/.checkpkg"
CHECKPKG_SITE_CONFIG_DIR = "/etc/opt/csw"
CHECKPKG_SRC_BASEDIR = os.path.dirname(__file__)
USER_CONFIG_FILE_TMPL = os.path.join(CHECKPKG_DIR, CHECKPKG_CONFIG_FILENAME)
AUTO_CONFIG_FILE_TMPL = os.path.join(CHECKPKG_DIR,
                                     CHECKPKG_AUTO_CONFIG_FILENAME)
SITE_CONFIG_FILE = os.path.join(CHECKPKG_SITE_CONFIG_DIR,
                                CHECKPKG_CONFIG_FILENAME)
SITE_CONFIG_RELMGR_FILE = os.path.join(CHECKPKG_SITE_CONFIG_DIR,
                                       CHECKPKG_RELMGR_CONFIG_FILENAME)
DEFAULTS_FILE = os.path.join(CHECKPKG_SRC_BASEDIR, CHECKPKG_DEFAULTS_FILENAME)

CONFIGURATION_FILE_LOCATIONS = [
    # Last entry wins.
    # The distinction between the autogenerated and user config file is
    # important.  The autogenerated file contains values that a user can
    # later change, but which can be overridden by the site-global config
    # file.  Think about a usage scenario, where a user first runs checkpkg on
    # a host on a buildfarm which happens to not have the site-global config
    # file.  When run, checkpkg will notice that there's no configuration and
    # will autogenerate the config file.  Now, when the same user (with the
    # same home directory) logs into a host that has the site-global config
    # file, it's expected that checkpkg will connect to the shared database.
    # To achieve this, the site-global config has to have priority over the
    # autogenerated file.
    (DEFAULTS_FILE,                True),
    (AUTO_CONFIG_FILE_TMPL,        False),
    (SITE_CONFIG_FILE,             False),
    (SITE_CONFIG_RELMGR_FILE,      False),
    (USER_CONFIG_FILE_TMPL,        False)
]

CONFIG_DEFAULTS = {
    'host': '',
    'user': '',
    'password': '',
    # Caching is disabled by default to conserve RAM. The buildfarm
    # infrastructure is suffering from OutOfMemory errors on systems with
    # e.g.  1GB or 1.5GB of RAM.
    'cache': 'false',
    'debug': 'false',
    'debugOutput': 'false',
}


class Error(Exception):
  "Generic error."


class ConfigurationError(Error):
  "A problem with configuration."


def MkdirP(p):
  try:
    os.makedirs(p)
  except OSError as e:
    if e.errno == errno.EEXIST: pass
    else: raise


def HomeExists():
  if "HOME" not in os.environ:
    return False
  return True


def GetConfig():
  # TODO(maciej): set defaults here in the constructor
  config = ConfigParser.SafeConfigParser(CONFIG_DEFAULTS)
  file_was_found = False
  filenames_read = []
  for file_name_tmpl, default_file in CONFIGURATION_FILE_LOCATIONS:
    filename = None
    try:
      filename = file_name_tmpl % os.environ
      if os.path.exists(filename):
        if not default_file:
          file_was_found = True
        filename_found = file_name_tmpl % os.environ
        filenames_read.append(filename_found)
        config.read(filename_found)
    except KeyError as e:
      logging.warn(e)
  if not file_was_found:
    if HomeExists():
      db_file = os.path.join(CHECKPKG_DIR % os.environ, "checkpkg.db")
      checkpkg_dir = CHECKPKG_DIR % os.environ
      MkdirP(checkpkg_dir)
      config_file = AUTO_CONFIG_FILE_TMPL % os.environ
      logging.warning(
          "No configuration file found.  Will attempt to create "
          "an sane default configuration in %s."
          % repr(config_file))
      if not config.has_section("database"):
        config.add_section("database")
      config.set("database", "type", "sqlite")
      config.set("database", "name", db_file)
      config.set("database", "host", "")
      config.set("database", "user", "")
      config.set("database", "password", "")
      if not config.has_section("rest"):
        config.add_section("rest")
      config.set("rest", "pkgdb", "http://localhost:8000")
      config.set("rest", "releases", "http://localhost:8001")
      if not config.has_section("buildfarm"):
        config.add_section("buildfarm")
      config.set("buildfarm", "catalog_root", "/export/opencsw")
      with open(config_file, "w") as fd:
        config.write(fd)
      logging.debug("Configuration has been written.")
  if not config.has_section("database"):
    logging.fatal(
        "Section 'database' not found in the config files: %s. "
        "Please refer to the documentation: "
        "http://wiki.opencsw.org/checkpkg" % filenames_read)
    raise SystemExit
  return config


def ComposeDatabaseUri(config, cache=False):
  db_data = {
      'db_type': config.get("database", "type"),
      'db_name': config.get("database", "name"),
      'db_host': config.get("database", "host"),
      'db_user': config.get("database", "user"),
      'db_password': config.get("database", "password"),
      'cache': config.get("database", "cache"),
      'debug': config.get("database", "debug"),
      'debugOutput': config.get("database", "debugOutput"),
  }
  display_db_data = dict(db_data)
  display_db_data['db_password'] = '******'
  logging.debug("db_data: %s" % display_db_data)
  if db_data["db_type"] == "mysql":
    db_uri_tmpl = ("%(db_type)s://%(db_user)s:%(db_password)s@%(db_host)s/"
                   "%(db_name)s?cache=%(cache)s")
  elif db_data["db_type"] == "sqlite":
    connector = '://'
    if db_data["db_name"] == ":memory:":
      connector = ':/'
    db_uri_tmpl = '%(db_type)s'
    db_uri_tmpl += connector
    db_uri_tmpl += '%(db_name)s?cache=%(cache)s'
    db_uri_tmpl += '&debug=%(debug)s'
    db_uri_tmpl += '&debugOutput=%(debugOutput)s'
  else:
    raise ConfigurationError(
        "Database type %r is not supported" % db_data["db_type"])
  db_uri = db_uri_tmpl % db_data
  return db_uri


def SetUpSqlobjectConnection():
  config = GetConfig()
  db_uri = ComposeDatabaseUri(config)
  sqo_conn = sqlobject.connectionForURI(db_uri)
  sqlobject.sqlhub.processConnection = sqo_conn

def TearDownSqlobjectConnection():
  sqlobject.sqlhub.processConnection = None
