// Copyright 2014 The Cayley 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 iterator

// "Value Comparison" is a unary operator -- a filter across the values in the
// relevant subiterator.
//
// This is hugely useful for things like provenance, but value ranges in general
// come up from time to time. At *worst* we're as big as our underlying iterator.
// At best, we're the null iterator.
//
// This is ripe for backend-side optimization. If you can run a value iterator,
// from a sorted set -- some sort of value index, then go for it.
//
// In MQL terms, this is the [{"age>=": 21}] concept.

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/google/cayley/graph"
)

type Operator int

const (
	kCompareLT Operator = iota
	kCompareLTE
	kCompareGT
	kCompareGTE
	// Why no Equals? Because that's usually an AndIterator.
)

type Comparison struct {
	Base
	subIt graph.Iterator
	op    Operator
	val   interface{}
	ts    graph.TripleStore
}

func NewComparison(sub graph.Iterator, op Operator, val interface{}, ts graph.TripleStore) *Comparison {
	var vc Comparison
	BaseInit(&vc.Base)
	vc.subIt = sub
	vc.op = op
	vc.val = val
	vc.ts = ts
	return &vc
}

// Here's the non-boilerplate part of the ValueComparison iterator. Given a value
// and our operator, determine whether or not we meet the requirement.
func (it *Comparison) doComparison(val graph.Value) bool {
	//TODO(barakmich): Implement string comparison.
	nodeStr := it.ts.NameOf(val)
	switch cVal := it.val.(type) {
	case int:
		cInt := int64(cVal)
		intVal, err := strconv.ParseInt(nodeStr, 10, 64)
		if err != nil {
			return false
		}
		return RunIntOp(intVal, it.op, cInt)
	case int64:
		intVal, err := strconv.ParseInt(nodeStr, 10, 64)
		if err != nil {
			return false
		}
		return RunIntOp(intVal, it.op, cVal)
	default:
		return true
	}
}

func (it *Comparison) Close() {
	it.subIt.Close()
}

func RunIntOp(a int64, op Operator, b int64) bool {
	switch op {
	case kCompareLT:
		return a < b
	case kCompareLTE:
		return a <= b
	case kCompareGT:
		return a > b
	case kCompareGTE:
		return a >= b
	default:
		log.Fatal("Unknown operator type")
		return false
	}
}

func (it *Comparison) Reset() {
	it.subIt.Reset()
}

func (it *Comparison) Clone() graph.Iterator {
	out := NewComparison(it.subIt.Clone(), it.op, it.val, it.ts)
	out.CopyTagsFrom(it)
	return out
}

func (it *Comparison) Next() (graph.Value, bool) {
	var val graph.Value
	var ok bool
	for {
		val, ok = it.subIt.Next()
		if !ok {
			return nil, false
		}
		if it.doComparison(val) {
			break
		}
	}
	it.Last = val
	return val, ok
}

func (it *Comparison) NextResult() bool {
	for {
		hasNext := it.subIt.NextResult()
		if !hasNext {
			return false
		}
		if it.doComparison(it.subIt.Result()) {
			return true
		}
	}
	it.Last = it.subIt.Result()
	return true
}

func (it *Comparison) Check(val graph.Value) bool {
	if !it.doComparison(val) {
		return false
	}
	return it.subIt.Check(val)
}

// If we failed the check, then the subiterator should not contribute to the result
// set. Otherwise, go ahead and tag it.
func (it *Comparison) TagResults(dst map[string]graph.Value) {
	it.Base.TagResults(dst)
	it.subIt.TagResults(dst)
}

// Registers the value-comparison iterator.
func (it *Comparison) Type() graph.Type { return graph.Comparison }

// Prints the value-comparison and its subiterator.
func (it *Comparison) DebugString(indent int) string {
	return fmt.Sprintf("%s(%s\n%s)",
		strings.Repeat(" ", indent),
		it.Type(), it.subIt.DebugString(indent+4))
}

// There's nothing to optimize, locally, for a value-comparison iterator.
// Replace the underlying iterator if need be.
// potentially replace it.
func (it *Comparison) Optimize() (graph.Iterator, bool) {
	newSub, changed := it.subIt.Optimize()
	if changed {
		it.subIt.Close()
		it.subIt = newSub
	}
	return it, false
}

// We're only as expensive as our subiterator.
// Again, optimized value comparison iterators should do better.
func (it *Comparison) Stats() graph.IteratorStats {
	return it.subIt.Stats()
}
