/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF 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 gremlingo

func convertStrategyVarargs(strategies []TraversalStrategy) []interface{} {
	converted := make([]interface{}, 0)
	for _, strategy := range strategies {
		converted = append(converted, strategy)
	}
	return converted
}

// GraphTraversalSource can be used to start GraphTraversal.
type GraphTraversalSource struct {
	graph            *Graph
	bytecode         *Bytecode
	remoteConnection *DriverRemoteConnection
	graphTraversal   *GraphTraversal
}

// NewGraphTraversalSource creates a new GraphTraversalSource from a Graph, DriverRemoteConnection, and any TraversalStrategy.
// Graph and DriverRemoteConnection can be set to nil as valid default values.
func NewGraphTraversalSource(graph *Graph, remoteConnection *DriverRemoteConnection,
	traversalStrategies ...TraversalStrategy) *GraphTraversalSource {
	convertedArgs := convertStrategyVarargs(traversalStrategies)
	bc := NewBytecode(nil)
	bc.AddSource("withStrategies", convertedArgs...)
	return &GraphTraversalSource{graph: graph, bytecode: bc, remoteConnection: remoteConnection}
}

// NewDefaultGraphTraversalSource creates a new graph GraphTraversalSource without a graph, strategy, or existing traversal.
func NewDefaultGraphTraversalSource() *GraphTraversalSource {
	return &GraphTraversalSource{graph: nil, bytecode: NewBytecode(nil), remoteConnection: nil}
}

// GetBytecode gets the traversal Bytecode associated with this graph traversal source.
func (gts *GraphTraversalSource) GetBytecode() *Bytecode {
	return gts.bytecode
}

// GetGraphTraversal gets the graph traversal associated with this graph traversal source.
func (gts *GraphTraversalSource) GetGraphTraversal() *GraphTraversal {
	return NewGraphTraversal(gts.graph, NewBytecode(gts.bytecode), gts.remoteConnection)
}

func (gts *GraphTraversalSource) clone() *GraphTraversalSource {
	return cloneGraphTraversalSource(gts.graph, NewBytecode(gts.bytecode), gts.remoteConnection)
}

func cloneGraphTraversalSource(graph *Graph, bc *Bytecode, remoteConnection *DriverRemoteConnection) *GraphTraversalSource {
	return &GraphTraversalSource{graph: graph,
		bytecode:         bc,
		remoteConnection: remoteConnection,
	}
}

// WithBulk allows for control of bulking operations.
func (gts *GraphTraversalSource) WithBulk(args ...interface{}) *GraphTraversalSource {
	source := gts.clone()
	err := source.bytecode.AddSource("withBulk", args...)
	if err != nil {
		return nil
	}
	return source
}

// WithPath adds a path to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithPath(args ...interface{}) *GraphTraversalSource {
	source := gts.clone()
	source.bytecode.AddSource("withPath", args...)
	return source
}

// WithSack adds a sack to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithSack(args ...interface{}) *GraphTraversalSource {
	source := gts.clone()
	// Force int32 serialization for valid number values for server compatibility
	source.bytecode.AddSource("withSack", int32Args(args)...)
	return source
}

// WithSideEffect adds a side effect to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithSideEffect(args ...interface{}) *GraphTraversalSource {
	source := gts.clone()
	source.bytecode.AddSource("withSideEffect", args...)
	return source
}

// WithStrategies adds an arbitrary collection of TraversalStrategies instances to the traversal source.
func (gts *GraphTraversalSource) WithStrategies(args ...TraversalStrategy) *GraphTraversalSource {
	convertedArgs := convertStrategyVarargs(args)
	source := gts.clone()
	source.bytecode.AddSource("withStrategies", convertedArgs...)
	return source
}

// WithoutStrategies removes an arbitrary collection of TraversalStrategies instances to the traversal source.
func (gts *GraphTraversalSource) WithoutStrategies(args ...TraversalStrategy) *GraphTraversalSource {
	convertedArgs := convertStrategyVarargs(args)
	source := gts.clone()
	source.bytecode.AddSource("withoutStrategies", convertedArgs...)
	return source
}

// With provides a configuration to a traversal in the form of a key value pair.
func (gts *GraphTraversalSource) With(key interface{}, value interface{}) *GraphTraversalSource {
	source := gts.clone()

	var optionsStrategy TraversalStrategy = nil
	for _, v := range gts.bytecode.sourceInstructions {
		if v.operator == "withStrategies" &&
			v.arguments[0].(*traversalStrategy).name == decorationNamespace+"OptionsStrategy" {
			optionsStrategy = v.arguments[0]
			break
		}
	}

	if optionsStrategy == nil {
		optionsStrategy = OptionsStrategy(map[string]interface{}{key.(string): value})
		return source.WithStrategies(optionsStrategy)
	}

	options := optionsStrategy.(*traversalStrategy)
	options.configuration[key.(string)] = value
	return source
}

// WithRemote adds a remote to be used throughout the life of a spawned Traversal.
func (gts *GraphTraversalSource) WithRemote(remoteConnection *DriverRemoteConnection) *GraphTraversalSource {
	gts.remoteConnection = remoteConnection
	if gts.graphTraversal != nil {
		gts.graphTraversal.remote = remoteConnection
	}
	return gts.clone()
}

// E reads edges from the graph to start the traversal.
func (gts *GraphTraversalSource) E(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("E", args...)
	return traversal
}

// V reads vertices from the graph to start the traversal.
func (gts *GraphTraversalSource) V(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("V", args...)
	return traversal
}

// AddE adds an Edge to start the traversal.
func (gts *GraphTraversalSource) AddE(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("addE", args...)
	return traversal
}

// AddV adds a Vertex to start the traversal.
func (gts *GraphTraversalSource) AddV(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("addV", args...)
	return traversal
}

// Call starts the traversal by executing a procedure
func (gts *GraphTraversalSource) Call(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("call", args...)
	return traversal
}

// Inject inserts arbitrary objects to start the traversal.
func (gts *GraphTraversalSource) Inject(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	// Force int32 serialization for valid number values for server compatibility
	traversal.Bytecode.AddStep("inject", int32Args(args)...)
	return traversal
}

// Io adds the io steps to start the traversal.
func (gts *GraphTraversalSource) Io(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("io", args...)
	return traversal
}

// MergeE uses an upsert-like operation to add an Edge to start the traversal.
func (gts *GraphTraversalSource) MergeE(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("mergeE", args...)
	return traversal
}

// MergeV uses an upsert-like operation to add a Vertex to start the traversal.
func (gts *GraphTraversalSource) MergeV(args ...interface{}) *GraphTraversal {
	traversal := gts.GetGraphTraversal()
	traversal.Bytecode.AddStep("mergeV", args...)
	return traversal
}

func (gts *GraphTraversalSource) Tx() *Transaction {
	return &Transaction{g: gts, remoteConnection: gts.remoteConnection}
}
