//
// DISCLAIMER
//
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//

package velocypack

// Merge creates a slice that contains all fields from all given slices.
// When a field exists (with same name) in an earlier slice, it is ignored.
// All slices must be objects.
func Merge(slices ...Slice) (Slice, error) {
	// Calculate overall length
	l := ValueLength(0)
	for _, s := range slices {
		if err := s.AssertType(Object); err != nil {
			return nil, WithStack(err)
		}
		byteSize, err := s.ByteSize()
		if err != nil {
			return nil, WithStack(err)
		}
		l += byteSize
	}

	if len(slices) == 1 {
		// Fast path, only 1 slice
		return slices[0], nil
	}

	// Create a buffer to hold all slices.
	b := NewBuilder(uint(l))
	keys := make(map[string]struct{})
	if err := b.OpenObject(); err != nil {
		return nil, WithStack(err)
	}
	for _, s := range slices {
		it, err := NewObjectIterator(s, true)
		if err != nil {
			return nil, WithStack(err)
		}
		for it.IsValid() {
			keySlice, err := it.Key(true)
			if err != nil {
				return nil, WithStack(err)
			}
			key, err := keySlice.GetString()
			if err != nil {
				return nil, WithStack(err)
			}
			if _, found := keys[key]; !found {
				// Record key
				keys[key] = struct{}{}

				// Fetch value
				value, err := it.Value()
				if err != nil {
					return nil, WithStack(err)
				}

				// Add key,value
				if err := b.addInternalKeyValue(key, NewSliceValue(value)); err != nil {
					return nil, WithStack(err)
				}
			}

			// Move to next field
			if err := it.Next(); err != nil {
				return nil, WithStack(err)
			}
		}
	}
	if err := b.Close(); err != nil {
		return nil, WithStack(err)
	}

	// Return slice
	result, err := b.Slice()
	if err != nil {
		return nil, WithStack(err)
	}
	return result, nil
}