// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 mapval

import "fmt"

// Results the results of executing a schema.
// They are a flattened map (using dotted paths) of all the values []ValueResult representing the results
// of the IsDefs.
type Results struct {
	Fields map[string][]ValueResult
	Valid  bool
}

// NewResults creates a new Results object.
func NewResults() *Results {
	return &Results{
		Fields: make(map[string][]ValueResult),
		Valid:  true,
	}
}

// SimpleResult provides a convenient and simple method for creating a *Results object for a single validation.
// It's a very common way for validators to return a *Results object, and is generally simpler than
// using SingleResult.
func SimpleResult(path Path, valid bool, msg string, args ...interface{}) *Results {
	vr := ValueResult{valid, fmt.Sprintf(msg, args...)}
	return SingleResult(path, vr)
}

// SingleResult returns a *Results object with a single validated value at the given path
// using the provided ValueResult as its sole validation.
func SingleResult(path Path, result ValueResult) *Results {
	r := NewResults()
	r.record(path, result)
	return r
}

func (r *Results) merge(other *Results) {
	for path, valueResults := range other.Fields {
		for _, valueResult := range valueResults {
			r.record(MustParsePath(path), valueResult)
		}
	}
}

func (r *Results) mergeUnderPrefix(prefix Path, other *Results) {
	if len(prefix) == 0 {
		// If the prefix is empty, just use standard merge
		// No need to add the dots
		r.merge(other)
		return
	}

	for path, valueResults := range other.Fields {
		for _, valueResult := range valueResults {
			parsed := MustParsePath(path)
			r.record(prefix.Concat(parsed), valueResult)
		}
	}
}

func (r *Results) record(path Path, result ValueResult) {
	if r.Fields[path.String()] == nil {
		r.Fields[path.String()] = []ValueResult{result}
	} else {
		r.Fields[path.String()] = append(r.Fields[path.String()], result)
	}

	if !result.Valid {
		r.Valid = false
	}
}

// EachResult executes the given callback once per Value result.
// The provided callback can return true to keep iterating, or false
// to stop.
func (r Results) EachResult(f func(Path, ValueResult) bool) {
	for path, pathResults := range r.Fields {
		for _, result := range pathResults {
			if !f(MustParsePath(path), result) {
				return
			}
		}
	}
}

// DetailedErrors returns a new Results object consisting only of error data.
func (r *Results) DetailedErrors() *Results {
	errors := NewResults()
	r.EachResult(func(path Path, vr ValueResult) bool {
		if !vr.Valid {
			errors.record(path, vr)
		}

		return true
	})
	return errors
}

// ValueResultError is used to represent an error validating an individual value.
type ValueResultError struct {
	path        Path
	valueResult ValueResult
}

// Error returns the error that occurred during validation with its context included.
func (vre ValueResultError) Error() string {
	return fmt.Sprintf("@path '%s': %s", vre.path, vre.valueResult.Message)
}

// Errors returns a list of error objects, one per failed value validation.
func (r Results) Errors() []error {
	errors := make([]error, 0)

	r.EachResult(func(path Path, vr ValueResult) bool {
		if !vr.Valid {
			errors = append(errors, ValueResultError{path, vr})
		}
		return true
	})

	return errors
}
