/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

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 tensorflow

// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
import "C"

import "unsafe"

// Operation that has been added to the graph.
type Operation struct {
	c *C.TF_Operation
	// A reference to the Graph to prevent it from
	// being GCed while the Operation is still alive.
	g *Graph
}

// Name returns the name of the operation.
func (op *Operation) Name() string {
	return C.GoString(C.TF_OperationName(op.c))
}

// Type returns the name of the operator used by this operation.
func (op *Operation) Type() string {
	return C.GoString(C.TF_OperationOpType(op.c))
}

// NumOutputs returns the number of outputs of op.
func (op *Operation) NumOutputs() int {
	return int(C.TF_OperationNumOutputs(op.c))
}

// Device returns a specification of the device on which this operation
// will be executed, or the empty string if there is no such specification.
func (op *Operation) Device() string {
	return C.GoString(C.TF_OperationDevice(op.c))
}

// OutputListSize returns the size of the list of Outputs that is produced by a
// named output of op.
//
// An Operation has multiple named outputs, each of which produces either
// a single tensor or a list of tensors. This method returns the size of
// the list of tensors for a specific output of the operation, identified
// by its name.
func (op *Operation) OutputListSize(output string) (int, error) {
	cname := C.CString(output)
	defer C.free(unsafe.Pointer(cname))
	status := newStatus()
	n := C.TF_OperationOutputListLength(op.c, cname, status.c)
	return int(n), status.Err()
}

// Output returns the i-th output of op.
func (op *Operation) Output(i int) Output {
	return Output{op, i}
}

// NumInputs returns the number of inputs of op.
func (op *Operation) NumInputs() int {
	return int(C.TF_OperationNumInputs(op.c))
}

// Output represents one of the outputs of an operation in the graph. Has a
// DataType (and eventually a Shape).  May be passed as an input argument to a
// function for adding operations to a graph, or to a Session's Run() method to
// fetch that output as a tensor.
type Output struct {
	// Op is the Operation that produces this Output.
	Op *Operation

	// Index specifies the index of the output within the Operation.
	Index int
}

// DataType returns the type of elements in the tensor produced by p.
func (p Output) DataType() DataType {
	return DataType(C.TF_OperationOutputType(p.c()))
}

// Shape returns the (possibly incomplete) shape of the tensor produced p.
func (p Output) Shape() Shape {
	status := newStatus()
	port := p.c()
	ndims := C.TF_GraphGetTensorNumDims(p.Op.g.c, port, status.c)
	if err := status.Err(); err != nil {
		// This should not be possible since an error only occurs if
		// the operation does not belong to the graph.  It should not
		// be possible to construct such an Operation object.
		return Shape{}
	}
	if ndims < 0 {
		return Shape{}
	}
	if ndims == 0 {
		return ScalarShape()
	}
	dims := make([]C.int64_t, ndims)
	C.TF_GraphGetTensorShape(p.Op.g.c, port, &dims[0], ndims, status.c)
	if err := status.Err(); err != nil {
		// Same as above, should not be possible.
		return Shape{}
	}
	ret := Shape{dims: make([]int64, ndims)}
	for i := 0; i < int(ndims); i++ {
		ret.dims[i] = int64(dims[i])
	}
	return ret
}

func (p Output) c() C.TF_Output {
	if p.Op == nil {
		// Attempt to provide a more useful panic message than "nil
		// pointer dereference".
		panic("nil-Operation. If the Output was created with a Scope object, see Scope.Err() for details.")
	}
	return C.TF_Output{oper: p.Op.c, index: C.int(p.Index)}
}

func (p Output) canBeAnInput() {}

// Consumers returns the inputs that consume this output.
func (p Output) Consumers() []Consumer {
	max := int(C.TF_OperationOutputNumConsumers(p.c()))
	if max == 0 {
		return nil
	}
	inputs := make([]C.TF_Input, max)
	n := C.TF_OperationOutputConsumers(p.c(), (*C.TF_Input)(unsafe.Pointer(&inputs[0])), C.int(max))
	inputs = inputs[:int(n)]

	var consumers []Consumer
	for _, consumer := range inputs {
		consumers = append(consumers, Consumer{
			Index: int(consumer.index),
			Op: &Operation{
				c: consumer.oper,
				g: p.Op.g,
			},
		})
	}

	return consumers
}

// Consumer identifies a specific input of an operation that consumes the output
// of another operation.
type Consumer struct {
	// Op is the Operation that is consuming the output of another operation.
	Op *Operation

	// Index is the index of the input within Op that the output of another
	// operation is connected to.
	Index int
}

func (p Consumer) c() C.TF_Input {
	if p.Op == nil {
		// Attempt to provide a more useful panic message than "nil
		// pointer dereference".
		panic("nil-Operation. Consumer objects should only be created by a call to Output.Consumers")
	}
	return C.TF_Input{oper: p.Op.c, index: C.int(p.Index)}
}

// DataType returns the type of the input.
func (p Consumer) DataType() DataType {
	return DataType(C.TF_OperationInputType(p.c()))
}

// Producer returns the Output that is connected to this Consumer.
func (p Consumer) Producer() Output {
	output := C.TF_OperationInput(p.c())
	return Output{
		Op: &Operation{
			c: output.oper,
			g: p.Op.g,
		},
		Index: int(output.index),
	}
}

// Input is the interface for specifying inputs to an operation being added to
// a Graph.
//
// Operations can have multiple inputs, each of which could be either a tensor
// produced by another operation (an Output object), or a list of tensors
// produced by other operations (an OutputList). Thus, this interface is
// implemented by both Output and OutputList.
//
// See OpSpec.Input for more information.
type Input interface {
	// Unexported to preclude implementations outside this package.
	canBeAnInput()
}

// OutputList represents a list of Outputs that can be provided as input to
// another operation.
type OutputList []Output

func (l OutputList) canBeAnInput() {}
