#!/usr/bin/env python2.7
# -*- coding: iso-8859-1 -*-
#
# $Id: 3146e78057582e96c2d0ea9f276166badd9668fe $

"""
Configuration classes for *sqlcmd*.

COPYRIGHT AND LICENSE

Copyright © 2008 Brian M. Clapper

This is free software, released under the following BSD-like license:

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. The end-user documentation included with the redistribution, if any,
   must include the following acknowlegement:

      This product includes software developed by Brian M. Clapper
      (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
      copyright © 2008 Brian M. Clapper.

    Alternately, this acknowlegement may appear in the software itself, if
    and wherever such third-party acknowlegements normally appear.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

$Id: 3146e78057582e96c2d0ea9f276166badd9668fe $
"""

# ---------------------------------------------------------------------------
# Imports
# ---------------------------------------------------------------------------

import logging
import os
import re
import sys

from grizzled import db, system
from grizzled.config import Configuration

from sqlcmd.exception import *

# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------

__all__ = ['SQLCmdConfig']

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------

log = logging.getLogger('sqlcmd.config')

# ---------------------------------------------------------------------------
# Classes
# ---------------------------------------------------------------------------

class DBInstanceConfigItem(object):
    """
    Captures information about a database configuration item read from the
    .sqlcmd file in the user's home directory.
    """
    def __init__(self,
                 section,
                 aliases,
                 host,
                 database,
                 user,
                 password,
                 type,
                 port,
                 on_connect,
                 config_dir):
        self.section = section
        self.aliases = aliases
        self.primary_alias = aliases[0]
        self.host = host
        self.database = database
        self.user = user
        self.password = password
        self.port = port
        self.db_type = type
        self.on_connect = None

        if on_connect:
            if on_connect[0] == '~':
                on_connect = os.path.expanduser(on_connect)

            if not os.path.isabs(on_connect):
                on_connect = os.path.abspath(os.path.join(config_dir,
                                                          on_connect))
            if not os.access(on_connect, os.R_OK|os.F_OK):
                log.warning('Database "%s": on-connect script "%s" either '
                            'does not exist, is not readable, or is not a '
                            'file.' % (database, on_connect))
                on_connect = None

            self.on_connect = on_connect

    @property
    def db_key(self):
        port = self.port if self.port else ''
        return '%s|%s|%s|%s' %\
               (self.host, self.db_type, self.port, self.database)

    def __hash__(self):
        return self.primary_alias.__hash__()

    def __str__(self):
        return 'host=%s, db=%s, type=%s' %\
               (self.host, self.database, self.db_type)

    def __repr__(self):
        return self.__str__()

class SQLCmdConfig(object):
    """ Data from the .sqlcmd file in the user's home directory"""

    def __init__(self, config_dir):
        self.__config = {}
        self.__config_dir = config_dir
        self.settings = {}

    def total_databases(self):
        return len(self.__config.keys())

    def load_file(self, path):
        self.path = path
        if os.access(path, os.R_OK|os.F_OK):
            cfg = Configuration()
            cfg.read(path)

            handler_table = (
                # section name regex       function
                # --------------------------------------------------
                (re.compile('^db\.'),      self.__config_db),
                (re.compile('^driver\.'),  self.__config_driver),
                (re.compile('^settings$'), self.__set_vars),
            )

            for section in cfg.sections:
                for regex, handler in handler_table:
                    if regex.match(section):
                        handler(cfg, section)

    def __config_db(self, cfg, section):
        primary_name = section[3:] # assumes it starts with 'db.'
        if len(primary_name) == 0:
            raise ConfigurationError('Bad database section name "%s"' % section)

        aliases = cfg.getlist(section, 'aliases', sep=',', optional=True)
        if aliases:
            aliases = [primary_name] + [a.strip() for a in aliases]
        else:
            aliases = []

        host = cfg.get(section, 'host', optional=True)
        port = cfg.get(section, 'port', optional=True)
        db_name = cfg.get(section, 'database')
        user = cfg.get(section, 'user', optional=True)
        password = cfg.get(section, 'password', optional=True)
        db_type = cfg.get(section, 'type')
        on_connect = cfg.get(section, 'onconnect', optional=True)

        aliases += [db_name]
        try:
            cfg_item = DBInstanceConfigItem(section,
                                            aliases,
                                            host,
                                            db_name,
                                            user,
                                            password,
                                            db_type,
                                            port,
                                            on_connect,
                                            self.__config_dir)
        except ValueError, msg:
            raise ConfigurationError(
                  'Configuration section [%s]: %s' % (section, msg)
            )

        for alias in aliases:
            self.__config[alias] = cfg_item

    def __config_driver(self, cfg, section):
        class_name = cfg.get(section, 'class')
        cls = system.class_for_name(class_name)
        name = cfg.get(section, 'name')
        db.add_driver(name, cls)

    def __set_vars(self, cfg, section):
        self.settings_section = section
        for option in cfg.options(section):
            self.settings[option] = cfg.get(section, option)

    def add(self, section, alias, host, port, database, type, user, password):
        try:
            self.__config[alias]
            raise ConfigurationError(
                'Alias "%s" is already in the configuration' % alias
            )

        except KeyError:
            try:
                cfg = DBInstanceConfigItem(section,
                                           [alias],
                                           host,
                                           database,
                                           user,
                                           password,
                                           type,
                                           port,
                                           None,
                                           self.__config_dir)
            except ValueError, msg:
                raise ConfigurationError(
                    'Error in configuration for alias "%s": %s' % (alias, msg)
                )
            self.__config[alias] = cfg

    def get(self, alias):
        return self.__config[alias]

    def get_aliases(self):
        aliases = self.__config.keys()
        aliases.sort()
        return aliases

    def find_match(self, alias):
        try:
            config_item = self.__config[alias]
            # Exact match. Use that one.
        except KeyError:
            # No match. Try to find one or more that come close.
            matches = {}
            for a in self.__config.keys():
                if a.startswith(alias):
                    config_item = self.__config[a]
                    matches[config_item.db_key] = config_item

            total_matches = len(matches)
            if total_matches == 0:
                raise ConfigurationError(
                    'No configuration item for database "%s"' % alias)
            if total_matches > 1:
                raise ConfigurationError(
                    '%d databases match partial alias "%s": %s' %\
                    (total_matches, alias, \
                     ', '.join([cfg.section for cfg in matches.values()]))
                )
            config_item = matches.values()[0]

        return config_item
