/*
Copyright 2017 The Kubernetes Authors.

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 strategy

import (
	"k8s.io/kubernetes/pkg/kubectl/apply"
)

// replaceVisitor creates a patch to replace a remote field value with a local field value
type replaceStrategy struct {
	strategic *delegatingStrategy
	options   Options
}

func createReplaceStrategy(options Options, strategic *delegatingStrategy) replaceStrategy {
	return replaceStrategy{
		strategic,
		options,
	}
}

// MergeList returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
	return v.doReplace(e)
}

// MergeMap returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
	return v.doReplace(e)
}

// MergeType returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
	return v.doReplace(e)
}

// MergePrimitive returns a result by merging the recorded, local and remote values
// - replacing the remote value with the local value
func (v replaceStrategy) MergePrimitive(e apply.PrimitiveElement) (apply.Result, error) {
	return v.doReplace(e)
}

// MergeEmpty
func (v replaceStrategy) MergeEmpty(e apply.EmptyElement) (apply.Result, error) {
	return apply.Result{Operation: apply.SET}, nil
}

// replace returns the local value if specified, otherwise it returns the remote value
// this works regardless of the approach
func (v replaceStrategy) doReplace(e apply.Element) (apply.Result, error) {

	if result, done := v.doAddOrDelete(e); done {
		return result, nil
	}
	if err := v.doConflictDetect(e); err != nil {
		return apply.Result{}, err
	}
	if e.HasLocal() {
		// Specified locally, set the local value
		return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, nil
	} else if e.HasRemote() {
		// Not specified locally, set the remote value
		return apply.Result{Operation: apply.SET, MergedResult: e.GetRemote()}, nil
	} else {
		// Only specified in the recorded, drop the field.
		return apply.Result{Operation: apply.DROP, MergedResult: e.GetRemote()}, nil
	}
}

// doAddOrDelete will check if the field should be either added or deleted.  If either is true, it will
// true the operation and true.  Otherwise it will return false.
func (v replaceStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
	if apply.IsAdd(e) {
		return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
	}

	// Delete the List
	if apply.IsDrop(e) {
		return apply.Result{Operation: apply.DROP}, true
	}

	return apply.Result{}, false
}

// doConflictDetect returns error if element has conflict
func (v replaceStrategy) doConflictDetect(e apply.Element) error {
	return v.strategic.doConflictDetect(e)
}

var _ apply.Strategy = &replaceStrategy{}
