package module

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"runtime"
)

// Err... are errors used globally
var (
	ErrUnknown = errors.New("Unknown error")
	// ErrRetrievingImage            = errors.New("Error retrieving image")
	// ErrInvalidURL                 = errors.New("Passed URL is not valid")
	// ErrDecodingImage              = errors.New("Error decoding image")
	// ErrWidthNoInteger             = errors.New("Error parsing width parameter as integer")
	// ErrHeightNoInteger            = errors.New("Error parsing height parameter as integer")
	// ErrAspectRatioWidthNoInteger  = errors.New("Error parsing aspect ratio width parameter as integer")
	// ErrAspectRatioHeightNoInteger = errors.New("Error parsing aspect ratio height parameter as integer")
	ErrReadingRequestBody = errors.New("Error reading request body")
	ErrDecodingJSON       = errors.New("Error decoding JSON")
	// ErrDecodingBase64             = errors.New("Error decoding base64 encoded image")
	// ErrReadingCroppedImage        = errors.New("Error reading cropped image")
	// ErrCreatingThumbnail          = errors.New("Error creating thumbnail")
	// ErrDecodingImageInternal      = errors.New("Error decoding image")
	// ErrEncodingImage              = errors.New("Error encoding image")
	ErrSendingResponse    = errors.New("Error sending response")
	ErrEncodingJSON       = errors.New("Error encoding JSON")
	ErrDatabase           = errors.New("Database error")
	ErrRetrievingTestData = errors.New("Error receiving Testdata")
	ErrParsingInt         = errors.New("Error parsing string to int")
	ErrReadingFile				= errors.New("Error reading file")
)

// StatusCodeMapper maps errors to HTTP Status codes
var StatusCodeMapper = map[error]int{
	ErrUnknown: http.StatusInternalServerError,
	// ErrRetrievingImage:            http.StatusBadRequest,
	// ErrInvalidURL:                 http.StatusBadRequest,
	// ErrDecodingImage:              http.StatusBadRequest,
	// ErrWidthNoInteger:             http.StatusBadRequest,
	// ErrHeightNoInteger:            http.StatusBadRequest,
	// ErrAspectRatioWidthNoInteger:  http.StatusBadRequest,
	// ErrAspectRatioHeightNoInteger: http.StatusBadRequest,
	ErrReadingRequestBody: http.StatusBadRequest,
	ErrDecodingJSON:       http.StatusBadRequest,
	// ErrDecodingBase64:             http.StatusBadRequest,
	// ErrReadingCroppedImage:        http.StatusInternalServerError,
	// ErrCreatingThumbnail:          http.StatusInternalServerError,
	// ErrDecodingImageInternal:      http.StatusInternalServerError,
	// ErrEncodingImage:              http.StatusInternalServerError,
	ErrSendingResponse:    http.StatusInternalServerError,
	ErrEncodingJSON:       http.StatusInternalServerError,
	ErrDatabase:           http.StatusInternalServerError,
	ErrRetrievingTestData: http.StatusBadGateway,
	ErrParsingInt:         http.StatusInternalServerError,
	ErrReadingFile: 				http.StatusInternalServerError,
}

// MetaError is two errors in one
type MetaError struct {
	outerError error
	innerError error
	httpStatus int
}

// NewMetaError returns a new MetaError and automatically selects the suited HTTP Status Code
func NewMetaError(outerError error, innerError error) MetaError {
	return MetaError{outerError, innerError, StatusCodeMapper[outerError]}
}

// NewMetaErrorWithStatus returns a new MetaError with manually selected HTTP Status Code
func NewMetaErrorWithStatus(outerError error, innerError error, httpStatus int) MetaError {
	return MetaError{outerError, innerError, httpStatus}
}

func (err MetaError) Error() string {
	return err.outerError.Error() + ": " + err.innerError.Error()
}

type StatusError struct {
	error
	httpStatus int
}

func NewStatusError(err error, status int) StatusError {
	return StatusError{err, status}
}

// HandleError will handle errors
func HandleError(outerErr error, innerErr error) {
	if innerErr != nil {

		switch outerErr {
		case ErrDatabase:
			switch innerErr.Error() {
			case "record not found":
				panic(NewStatusError(innerErr, http.StatusNotFound))
			}
		}

		panic(NewMetaError(outerErr, innerErr))
	}
}

// HTTPStatusHandler ...
type HTTPStatusHandler struct {
	Debug bool // TODO implement
}

// NewHTTPStatusHandler ...
func NewHTTPStatusHandler() *HTTPStatusHandler {
	return &HTTPStatusHandler{
		Debug: false,
	}
}

// HTTPStatusRecovery catches errors and serves http statuses
func (rec *HTTPStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	defer func() {
		if err := recover(); err != nil {
			// get the stack traces and print it out
			// stack := stack(0)
			// log.Printf("PANIC: %s\n%s", err, stack)

			// convert err to a string
			var errMsg string
			if e, ok := err.(error); ok {
				errMsg = e.Error()
			} else {
				errMsg = fmt.Sprint(err)
			}

			// Logging
			_, file, line, _ := runtime.Caller(4)
			log.Printf("%s:%d: %s", file, line, errMsg)

			errText, jErr := json.Marshal(map[string]string{"status": errMsg})
			if jErr != nil {
				log.Println(jErr)
				// TODO handle error
			}

			if _, ok := err.(MetaError); ok {
				http.Error(w, string(errText), err.(MetaError).httpStatus)
			} else if _, ok = err.(StatusError); ok {
				http.Error(w, string(errText), err.(StatusError).httpStatus)
			} else {
				http.Error(w, string(errText), http.StatusInternalServerError)
			}
		}
	}()

	next(w, r)
}

// Log logs
func Log(text string) {
	log.Println(text)
}