package db

import (
    "fmt"
    "time"

    "github.com/lib/pq"
)

// ImproperlyInitializedError documents why a initializing a db connection
// failed.
type ImproperlyInitializedError struct {
    Mesg string
}

// ConnectionError signifies the given database could not be connected to and
// returns the underlying error.
type ConnectionError struct {
    UnderlyingError error
}

// ConnectionTimeoutError signifies the given database could not be connected
// to within the given duration.
type ConnectionTimeoutError struct {
    ConnectString string
    Timeout       time.Duration
}

// QueryFailedError reports that the given query failed for the reason
// documented in the UnderlyingError.
type QueryFailedError struct {
    Query           string
    UnderlyingError error
}

// QueryTimedOutError reports that a query has timed out.
type QueryTimedOutError struct {
    QueryString string
    Timeout     time.Duration
}

type IntegrityViolationType uint
// Types of db errors possibly returned in IntegrityViolationError
const (
    INTEGRITY_CONSTRAINT_VIOLATION IntegrityViolationType = iota
    RESTRICT_VIOLATION
    NOT_NULL_VIOLATION
    FOREIGN_KEY_VIOLATION
    UNIQUE_VIOLATION
    CHECK_VIOLATION
    EXCLUSION_VIOLATION
)

var (
    integrityViolationErrs = map[string]IntegrityViolationType {
                                 "23000": INTEGRITY_CONSTRAINT_VIOLATION,
                                 "23001": RESTRICT_VIOLATION,
                                 "23502": NOT_NULL_VIOLATION,
                                 "23503": FOREIGN_KEY_VIOLATION,
                                 "23505": UNIQUE_VIOLATION,
                                 "23514": CHECK_VIOLATION,
                                 "23P01": EXCLUSION_VIOLATION }
)

// IntegrityViolationError signifies that a query partially or fully completed,
// but generated a SQL integrity violation (such as a key constraint violation,
// or a not null violation).  The type of constraint violation can be found
// using the IntegrityViolationType enumeration.
type IntegrityViolationError struct {
    QueryFailedError
    Type IntegrityViolationType
}


func (e ImproperlyInitializedError) Error() string {
    return e.Mesg
}

func (e ConnectionError) Error() string {
    return fmt.Sprintf("Got error while connecting to db: %s", e.UnderlyingError)
}

func (e QueryFailedError) Error() string {
    return fmt.Sprintf("Failed query: %s\nError:%s", e.Query, e.UnderlyingError)
}

func (e QueryTimedOutError) Error() string {
    return fmt.Sprintf("Query timed out after %f seconds:\n  %s",
                       float64(e.Timeout) / float64(time.Second),
                       e.QueryString)
}

func returnQueryError(err error, query string) error {
    if pgErr, ok := err.(pq.PGError); ok {
        errCode := pgErr.Get('C')
        switch(errCode[0:2]) {
        case "23":
            return IntegrityViolationError{QueryFailedError: QueryFailedError{Query: query, UnderlyingError: err},
                                           Type: integrityViolationErrs[errCode]}
        default:
            return QueryFailedError{Query: query, UnderlyingError: err}
        }
    }

    return nil
}

func (e ConnectionTimeoutError) Error() string {
    return fmt.Sprintf("Could not connect to DB in %f seconds; connect string:\n  %s",
                        float64(e.Timeout) / float64(time.Second),
                        e.ConnectString)
}
