/*
 * EliasDB
 *
 * Copyright 2016 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package interpreter

import (
	"devt.de/krotik/eliasdb/eql/parser"
	"devt.de/krotik/eliasdb/graph"
)

// Runtime provider for GET queries
// ================================

/*
Instance function for GET query components
*/
type getInst func(*GetRuntimeProvider, *parser.ASTNode) parser.Runtime

/*
Runtime map for GET query specific components
*/
var getProviderMap = map[string]getInst{
	parser.NodeGET: getRuntimeInst,
}

/*
GetRuntimeProvider data structure
*/
type GetRuntimeProvider struct {
	*eqlRuntimeProvider
}

/*
NewGetRuntimeProvider creates a new GetRuntimeProvider object. This provider
can interpret GET queries.
*/
func NewGetRuntimeProvider(name string, part string, gm *graph.Manager, ni NodeInfo) *GetRuntimeProvider {
	return &GetRuntimeProvider{&eqlRuntimeProvider{name, part, gm, ni, "", false, nil, "",
		nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}}
}

/*
Runtime returns a runtime component for a given ASTNode.
*/
func (rtp *GetRuntimeProvider) Runtime(node *parser.ASTNode) parser.Runtime {
	if pinst, ok := generalProviderMap[node.Name]; ok {
		return pinst(rtp.eqlRuntimeProvider, node)
	} else if pinst, ok := getProviderMap[node.Name]; ok {
		return pinst(rtp, node)
	}
	return invalidRuntimeInst(rtp.eqlRuntimeProvider, node)
}

// GET Runtime
// ===========

type getRuntime struct {
	rtp  *GetRuntimeProvider
	node *parser.ASTNode
}

func getRuntimeInst(rtp *GetRuntimeProvider, node *parser.ASTNode) parser.Runtime {
	return &getRuntime{rtp, node}
}

/*
 Validate and reset this runtime component and all its child components.
*/
func (rt *getRuntime) Validate() error {

	// First child is always the first node kind to query
	// (validation of this value was done during lexing)

	startKind := rt.node.Children[0].Token.Val

	initErr := rt.rtp.init(startKind, rt.node.Children[1:])

	if rt.rtp.groupScope == "" {

		// Start keys can be provided by a simple node key iterator

		startKeyIterator, err := rt.rtp.gm.NodeKeyIterator(rt.rtp.part, startKind)

		if err != nil {
			return err
		} else if startKeyIterator == nil {
			return rt.rtp.newRuntimeError(ErrUnknownNodeKind, startKind, rt.node.Children[0])
		}

		rt.rtp.nextStartKey = func() (string, error) {
			nextKey := startKeyIterator.Next()
			if startKeyIterator.LastError != nil {
				return "", startKeyIterator.LastError
			}
			return nextKey, nil
		}

	} else {

		// Try to lookup group node

		nodes, _, err := rt.rtp.gm.TraverseMulti(rt.rtp.part, rt.rtp.groupScope,
			GroupNodeKind, ":::"+startKind, false)

		if err != nil {
			return err
		}

		nodePtr := len(nodes)

		// Iterate over all traversed nodes

		rt.rtp.nextStartKey = func() (string, error) {
			nodePtr--

			if nodePtr >= 0 {
				return nodes[nodePtr].Key(), nil

			}

			return "", nil
		}
	}

	return initErr
}

/*
Eval evaluate this runtime component.
*/
func (rt *getRuntime) Eval() (interface{}, error) {

	// First validate the query and reset the runtime provider datastructures

	if rt.rtp.specs == nil || !allowMultiEval {
		if err := rt.Validate(); err != nil {
			return nil, err
		}
	}

	return rt.gaterResult(rt.node)
}

func (rt *getRuntime) gaterResult(topNode *parser.ASTNode) (interface{}, error) {

	// Generate query

	query, err := parser.PrettyPrint(topNode)

	// Create result object

	res := newSearchResult(rt.rtp.eqlRuntimeProvider, query)

	if err == nil {
		var more bool

		// Go through all rows

		more, err = rt.rtp.next()
		for more && err == nil {

			// Add row to the result

			if err := res.addRow(rt.rtp.rowNode, rt.rtp.rowEdge); err != nil {
				return nil, err
			}

			// More on to the next row

			more, err = rt.rtp.next()
		}

		// Finish the result

		res.finish()
	}

	return res, err
}
