# MySQL Connector/Python - MySQL driver written in Python.
# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved.
# Use is subject to license terms. (See COPYING)

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
# 
# There are special exceptions to the terms and conditions of the GNU
# General Public License as it is applied to this software. View the
# full text of the exception in file EXCEPTIONS-CLIENT in the directory
# of this software distribution or see the FOSS License Exception at
# www.mysql.com.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Python exceptions
"""

import logging

from . import utils

logger = logging.getLogger('myconnpy')

# see get_mysql_exception method for errno ranges and smaller lists
__programming_errors = (
    1083,1084,1090,1091,1093,1096,1097,1101,1102,1103,1107,1108,1110,1111,
    1113,1120,1124,1125,1128,1136,1366,1139,1140,1146,1149,)
__operational_errors = (
    1028,1029,1030,1053,1077,1078,1079,1080,1081,1095,1104,1106,1114,1116,
    1117,1119,1122,1123,1126,1133,1135,1137,1145,1147,)
    
def get_mysql_exception(errno,msg):

    exception = OperationalError

    if (errno >= 1046 and errno <= 1052) or \
        (errno >= 1054 and errno <= 1061) or \
        (errno >= 1063 and errno <= 1075) or \
        errno in __programming_errors:
        exception = ProgrammingError
    elif errno in (1097,1109,1118,1121,1138,1292):
        exception = DataError
    elif errno in (1031,1089,1112,1115,1127,1148,1149):
        exception = NotSupportedError
    elif errno in (1062,1082,1099,1100):
        exception = IntegrityError
    elif errno in (1085,1086,1094,1098):
        exception = InternalError
    elif (errno >= 1004 and errno <= 1030) or \
        (errno >= 1132 and errno <= 1045) or \
        (errno >= 1141 and errno <= 1145) or \
        (errno >= 1129 and errno <= 1133) or \
        errno in __operational_errors:
        exception = OperationalError

    return exception(msg,errno=errno)

def raise_error(buf):
    """Raise an errors.Error when buffer has a MySQL error"""
    errno = errmsg = None
    try:
        buf = buf[5:]
        (buf,errno) = utils.read_int(buf, 2)
        if buf[0] != 35:
            # Error without SQLState
            errmsg = buf
        else:
            (buf,sqlstate) = utils.read_bytes(buf[1:],5)
            errmsg = buf.decode('utf-8')
            sqlstate = sqlstate.decode('utf-8')
    except Exception as e:
        raise InterfaceError("Failed getting Error information (%r)"\
            % e)
    else:
        raise get_mysql_exception(errno,errmsg)

class ClientError(object):
    
    client_errors = {
        2000: "Unknown MySQL error",
        2001: "Can't create UNIX socket (%(socketaddr)d)",
        2002: "Can't connect to local MySQL server through socket '%(socketaddr)s' (%(errno)s)",
        2003: "Can't connect to MySQL server on '%(socketaddr)s' (%(errno)s)",
        2004: "Can't create TCP/IP socket (%s)",
        2005: "Unknown MySQL server host '%(socketaddr)s' (%s)",
        2006: "MySQL server has gone away",
        2007: "Protocol mismatch; server version = %(server_version)d, client version = %(client_version)d",
        2008: "MySQL client ran out of memory",
        2009: "Wrong host info",
        2010: "Localhost via UNIX socket",
        2011: "%(misc)s via TCP/IP",
        2012: "Error in server handshake",
        2013: "Lost connection to MySQL server during query",
        2014: "Commands out of sync; you can't run this command now",
        2015: "Named pipe: %(socketaddr)s",
        2016: "Can't wait for named pipe to host: %(host)s  pipe: %(socketaddr)s (%(errno)d)",
        2017: "Can't open named pipe to host: %s  pipe: %s (%(errno)d)",
        2018: "Can't set state of named pipe to host: %(host)s  pipe: %(socketaddr)s (%(errno)d)",
        2019: "Can't initialize character set %(charset)s (path: %(misc)s)",
        2020: "Got packet bigger than 'max_allowed_packet' bytes",
        2021: "Embedded server",
        2022: "Error on SHOW SLAVE STATUS:",
        2023: "Error on SHOW SLAVE HOSTS:",
        2024: "Error connecting to slave:",
        2025: "Error connecting to master:",
        2026: "SSL connection error",
        2027: "Malformed packet",
        2028: "This client library is licensed only for use with MySQL servers having '%s' license",
        2029: "Invalid use of null pointer",
        2030: "Statement not prepared",
        2031: "No data supplied for parameters in prepared statement",
        2032: "Data truncated",
        2033: "No parameters exist in the statement",
        2034: "Invalid parameter number",
        2035: "Can't send long data for non-string/non-binary data types (parameter: %d)",
        2036: "Using unsupported buffer type: %d  (parameter: %d)",
        2037: "Shared memory: %s",
        2038: "Can't open shared memory; client could not create request event (%d)",
        2039: "Can't open shared memory; no answer event received from server (%d)",
        2040: "Can't open shared memory; server could not allocate file mapping (%d)",
        2041: "Can't open shared memory; server could not get pointer to file mapping (%d)",
        2042: "Can't open shared memory; client could not allocate file mapping (%d)",
        2043: "Can't open shared memory; client could not get pointer to file mapping (%d)",
        2044: "Can't open shared memory; client could not create %s event (%d)",
        2045: "Can't open shared memory; no answer from server (%d)",
        2046: "Can't open shared memory; cannot send request event to server (%d)",
        2047: "Wrong or unknown protocol",
        2048: "Invalid connection handle",
        2049: "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)",
        2050: "Row retrieval was canceled by mysql_stmt_close() call",
        2051: "Attempt to read column without prior row fetch",
        2052: "Prepared statement contains no metadata",
        2053: "Attempt to read a row while there is no result set associated with the statement",
        2054: "This feature is not implemented yet",
        2055: "Lost connection to MySQL server at '%(socketaddr)s', system error: %(errno)d",
        2056: "Statement closed indirectly because of a preceeding %s() call",
        2057: "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again",
    }
    
    def __new__(cls):
        raise TypeError("Can not instanciate from {name}".format(name=cls.__name__))
    
    @classmethod
    def get_error_msg(cls,errno,values=None):
        res = None
        if errno in cls.client_errors:
            if values is not None:
                try:
                    res = cls.client_errors[errno] % values
                except:
                    logger.debug("Missing values for errno %d" % errno)
                    res = cls.client_errors[errno], "(missing values!)"
            else:
                res = cls.client_errors[errno]
        if res is None:
            res = "Unknown client error %d" % errno
            logger.debug(res)
        return res

class Error(Exception):
    
    def __init__(self, m, errno=None, values=None):
        try:
            # process MySQL error packet
            self._process_packet(m)
        except:
            self.errno = errno or -1
            self.sqlstate = -1
            if m is None and (errno >= 2000 and errno < 3000):
                m = ClientError.get_error_msg(errno,values)
            elif m is None:
                m = 'Unknown error'
            self.msg = "%s: %s" % (self.errno,m)
            
    def _process_packet(self, packet):
        self.errno = packet.errno
        self.sqlstate = packet.sqlstate
        if self.sqlstate:
            self.msg = '%d (%s): %s' % (self.errno,self.sqlstate,packet.errmsg)
        else:
            self.msg = '%d: %s' % (self.errno,packet.errmsg)
    
    def __str__(self):
        return self.msg
        
    def __unicode__(self):
        return self.msg
        
class Warning(Exception):
    pass

class InterfaceError(Error):
    def __init__(self, msg=None, errno=None, values=None):
        Error.__init__(self, msg, errno, values)

class DatabaseError(Error):
    def __init__(self, msg=None, errno=None, values=None):
        Error.__init__(self, msg, errno, values)

class InternalError(DatabaseError):
    pass

class OperationalError(DatabaseError):
    pass

class ProgrammingError(DatabaseError):
    pass

class IntegrityError(DatabaseError):
    pass

class DataError(DatabaseError):
    pass

class NotSupportedError(DatabaseError):
    pass
