// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mapper

import (
	"fmt"
	"reflect"
)

// MappingError represents a collection of decoding errors, defined below.
type MappingError interface {
	error
	Failures() []error    // the full set of errors (each of one of the below types).
	AddFailure(err error) // registers a new failure.
}

// mappingError is a concrete implementation of MappingError; it is private, and we prefer to use the above interface
// type, to avoid tricky non-nil nils in common usage patterns (see https://golang.org/doc/faq#nil_error).
type mappingError struct {
	failures []error
}

var _ error = (*mappingError)(nil) // ensure this implements the error interface.

func NewMappingError(errs []error) MappingError {
	return &mappingError{failures: errs}
}

func (e *mappingError) Failures() []error { return e.failures }

func (e *mappingError) AddFailure(err error) {
	e.failures = append(e.failures, err)
}

func (e *mappingError) Error() string {
	str := fmt.Sprintf("%d failures decoding:", len(e.failures))
	for _, failure := range e.failures {
		switch f := failure.(type) {
		case FieldError:
			str += fmt.Sprintf("\n\t%v: %v", f.Field(), f.Reason())
		default:
			str += fmt.Sprintf("\n\t%v", f)
		}
	}
	return str
}

// FieldError represents a failure during decoding of a specific field.
type FieldError interface {
	error
	Field() string  // returns the name of the field with a problem.
	Reason() string // returns a full diagnostic string about the error.
}

// fieldError is used when a general purpose error occurs decoding a field.
type fieldError struct {
	Type    string
	Fld     string
	Message string
}

var (
	_ error      = (*fieldError)(nil) // ensure this implements the error interface.
	_ FieldError = (*fieldError)(nil) // ensure this implements the fieldError interface.
)

func NewFieldError(ty string, fld string, err error) FieldError {
	return &fieldError{
		Type:    ty,
		Fld:     fld,
		Message: fmt.Sprintf("An error occurred decoding '%v.%v': %v", ty, fld, err),
	}
}

func NewTypeFieldError(ty reflect.Type, fld string, err error) FieldError {
	return NewFieldError(ty.Name(), fld, err)
}

func (e *fieldError) Error() string  { return e.Message }
func (e *fieldError) Field() string  { return e.Fld }
func (e *fieldError) Reason() string { return e.Message }

// MissingError is used when a required field is missing on an object of a given type.
type MissingError struct {
	Type    reflect.Type
	Fld     string
	Message string
}

var (
	_ error      = (*MissingError)(nil) // ensure this implements the error interface.
	_ FieldError = (*MissingError)(nil) // ensure this implements the FieldError interface.
)

func NewMissingError(ty reflect.Type, fld string) *MissingError {
	return &MissingError{
		Type:    ty,
		Fld:     fld,
		Message: fmt.Sprintf("Missing required field '%v' on '%v'", fld, ty),
	}
}

func (e *MissingError) Error() string  { return e.Message }
func (e *MissingError) Field() string  { return e.Fld }
func (e *MissingError) Reason() string { return e.Message }

// UnrecognizedError is used when a field is unrecognized on the given type.
type UnrecognizedError struct {
	Type    reflect.Type
	Fld     string
	Message string
}

var (
	_ error      = (*UnrecognizedError)(nil) // ensure this implements the error interface.
	_ FieldError = (*UnrecognizedError)(nil) // ensure this implements the FieldError interface.
)

func NewUnrecognizedError(ty reflect.Type, fld string) *UnrecognizedError {
	return &UnrecognizedError{
		Type:    ty,
		Fld:     fld,
		Message: fmt.Sprintf("Unrecognized field '%v' on '%v'", fld, ty),
	}
}

func (e *UnrecognizedError) Error() string  { return e.Message }
func (e *UnrecognizedError) Field() string  { return e.Fld }
func (e *UnrecognizedError) Reason() string { return e.Message }

type WrongTypeError struct {
	Type    reflect.Type
	Fld     string
	Expect  reflect.Type
	Actual  reflect.Type
	Message string
}

var (
	_ error      = (*WrongTypeError)(nil) // ensure this implements the error interface.
	_ FieldError = (*WrongTypeError)(nil) // ensure this implements the FieldError interface.
)

func NewWrongTypeError(ty reflect.Type, fld string, expect reflect.Type, actual reflect.Type) *WrongTypeError {
	return &WrongTypeError{
		Type:   ty,
		Fld:    fld,
		Expect: expect,
		Actual: actual,
		Message: fmt.Sprintf(
			"Field '%v' on '%v' must be a '%v'; got '%v' instead", fld, ty, expect, actual),
	}
}

func (e *WrongTypeError) Error() string  { return e.Message }
func (e *WrongTypeError) Field() string  { return e.Fld }
func (e *WrongTypeError) Reason() string { return e.Message }