// Copyright © 2016 Abcum Ltd
//
// 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 db

import (
	"context"

	"github.com/abcum/surreal/cnf"
	"github.com/abcum/surreal/sql"
	"github.com/abcum/surreal/util/data"
)

func (d *document) check(ctx context.Context, cond sql.Expr) (ok bool, err error) {

	// If no condition expression has been
	// defined then we can ignore this, and
	// process the current document.

	if cond == nil {
		return true, nil
	}

	// If a condition expression has been
	// defined then let's process it to see
	// what value it returns or error.

	val, err := d.i.e.fetch(ctx, cond, d.current)

	// If the condition expression result is
	// not a boolean value, then let's see
	// if the value can be equated to a bool.

	return calcAsBool(val), err

}

// Grant checks to see if the table permissions allow
// this record to be accessed for live queries, and
// if not then it errors accordingly.
func (d *document) grant(ctx context.Context, when method) (ok bool, err error) {

	var val interface{}

	// If we are authenticated using DB, NS,
	// or KV permissions level, then we can
	// ignore all permissions checks, but we
	// must ensure the TB, DB, and NS exist.

	if k, ok := ctx.Value(ctxKeyKind).(cnf.Kind); ok {
		if k < cnf.AuthSC {
			return true, nil
		}
	}

	// Otherwise, get the table definition
	// so we can check if the permissions
	// allow us to view this document.

	tb, err := d.getTB()
	if err != nil {
		return false, err
	}

	// Once we have the table we reset the
	// context to DB level so that no other
	// embedded permissions are checked on
	// records within these permissions.

	ctx = context.WithValue(ctx, ctxKeyKind, cnf.AuthDB)

	// We then try to process the relevant
	// permissions dependent on the query
	// that we are currently processing. If
	// there are no permissions specified
	// for this table, then because this is
	// a scoped request, return an error.

	switch p := tb.Perms.(type) {
	case *sql.PermExpression:
		switch when {
		case _CREATE:
			val, err = d.i.e.fetch(ctx, p.Select, d.current)
		case _UPDATE:
			val, err = d.i.e.fetch(ctx, p.Select, d.current)
		case _DELETE:
			val, err = d.i.e.fetch(ctx, p.Select, d.initial)
		}
	}

	// If the permissions expressions
	// returns a boolean value, then we
	// return this, dictating whether the
	// document is able to be viewed.

	if val, ok := val.(bool); ok {
		return val, err
	}

	// Otherwise as this request is scoped,
	// return an error, so that the
	// document is unable to be viewed.

	return false, err

}

// Query checks to see if the table permissions allow
// this record to be accessed for normal queries, and
// if not then it errors accordingly.
func (d *document) allow(ctx context.Context, when method) (ok bool, err error) {

	var val interface{}

	// If we are authenticated using DB, NS,
	// or KV permissions level, then we can
	// ignore all permissions checks, but we
	// must ensure the TB, DB, and NS exist.

	if k, ok := ctx.Value(ctxKeyKind).(cnf.Kind); ok {
		if k < cnf.AuthSC {
			return true, nil
		}
	}

	// Otherwise, get the table definition
	// so we can check if the permissions
	// allow us to view this document.

	tb, err := d.getTB()
	if err != nil {
		return false, err
	}

	// Once we have the table we reset the
	// context to DB level so that no other
	// embedded permissions are checked on
	// records within these permissions.

	ctx = context.WithValue(ctx, ctxKeyKind, cnf.AuthDB)

	// We then try to process the relevant
	// permissions dependent on the query
	// that we are currently processing. If
	// there are no permissions specified
	// for this table, then because this is
	// a scoped request, return an error.

	switch p := tb.Perms.(type) {
	case *sql.PermExpression:
		switch when {
		case _SELECT:
			val, err = d.i.e.fetch(ctx, p.Select, d.current)
		case _CREATE:
			val, err = d.i.e.fetch(ctx, p.Create, d.current)
		case _UPDATE:
			val, err = d.i.e.fetch(ctx, p.Update, d.current)
		case _DELETE:
			val, err = d.i.e.fetch(ctx, p.Delete, d.current)
		}
	}

	// If the permissions expressions
	// returns a boolean value, then we
	// return this, dictating whether the
	// document is able to be viewed.

	if val, ok := val.(bool); ok {
		return val, err
	}

	// Otherwise as this request is scoped,
	// return an error, so that the
	// document is unable to be viewed.

	return false, err

}

// Event checks if any triggers are specified for this
// table, and executes them in name order.
func (d *document) event(ctx context.Context, when method) (err error) {

	// Get the index values specified
	// for this table, loop through
	// them, and compute the changes.

	evs, err := d.getEV()
	if err != nil {
		return err
	}

	if len(evs) > 0 {

		kind := ""

		switch when {
		case _CREATE:
			kind = "CREATE"
		case _UPDATE:
			kind = "UPDATE"
		case _DELETE:
			kind = "DELETE"
		}

		vars := data.New()
		vars.Set(d.id, varKeyThis)
		vars.Set(kind, varKeyMethod)
		vars.Set(d.current.Data(), varKeyAfter)
		vars.Set(d.initial.Data(), varKeyBefore)
		ctx = context.WithValue(ctx, ctxKeySpec, vars)

		for _, ev := range evs {

			val, err := d.i.e.fetch(ctx, ev.When, d.current)
			if err != nil {
				return err
			}

			switch v := val.(type) {
			case bool:
				switch v {
				case true:
					_, err = d.i.e.fetch(ctx, ev.Then, d.current)
					if err != nil {
						return err
					}
				}
			}

		}

	}

	return

}

func (d *document) yield(ctx context.Context, stm sql.Statement, output sql.Token) (interface{}, error) {

	switch stm := stm.(type) {

	case *sql.SelectStatement:

		var doc *data.Doc

		for _, v := range stm.Expr {
			if _, ok := v.Expr.(*sql.All); ok {
				doc = d.current
				break
			}
		}

		if doc == nil {
			doc = data.New()
		}

		for _, e := range stm.Expr {

			switch v := e.Expr.(type) {
			case *sql.All:
				break
			default:

				// If the query has a GROUP BY expression
				// then let's check if this is an aggregate
				// function, and if it is then pass the
				// first argument directly through.

				if len(stm.Group) > 0 {
					if f, ok := e.Expr.(*sql.FuncExpression); ok && f.Aggr {
						v, err := d.i.e.fetch(ctx, f.Args[0], d.current)
						if err != nil {
							return nil, err
						}
						doc.Set(v, f.String())
						continue
					}
				}

				// Otherwise treat the field normally, and
				// calculate the value to be inserted into
				// the final output document.

				v, err := d.i.e.fetch(ctx, v, d.current)
				if err != nil {
					return nil, err
				}

				switch v {
				case d.current:
					doc.Set(nil, e.Field)
				default:
					doc.Set(v, e.Field)
				}

			}

		}

		return doc.Data(), nil

	default:

		switch output {
		default:
			return nil, nil
		case sql.DIFF:
			return d.diff().Data(), nil
		case sql.AFTER:
			return d.current.Data(), nil
		case sql.BEFORE:
			return d.initial.Data(), nil
		case sql.BOTH:
			return map[string]interface{}{
				"after":  d.current.Data(),
				"before": d.initial.Data(),
			}, nil
		}

	}

}