// 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 item

import (
	"fmt"
	"reflect"

	"github.com/abcum/surreal/kvs"
	"github.com/abcum/surreal/sql"
	"github.com/abcum/surreal/util/data"
	"github.com/abcum/surreal/util/keys"
	"github.com/abcum/surreal/util/pack"
)

type Doc struct {
	kv      kvs.KV
	tx      kvs.TX
	id      *sql.Thing
	key     *keys.Thing
	initial *data.Doc
	current *data.Doc
	runtime *data.Doc
	fields  []*sql.DefineFieldStatement
	indexs  []*sql.DefineIndexStatement
	rules   map[string]*sql.DefineRulesStatement
}

func New(kv kvs.KV, tx kvs.TX, key *keys.Thing, vars *data.Doc) (this *Doc) {

	this = &Doc{kv: kv, key: key, tx: tx, runtime: vars}

	if key == nil {
		this.key = &keys.Thing{}
		this.key.Decode(kv.Key())
	}

	if kv.Exists() == false {
		this.initial = data.New()
		this.current = data.New()
	}

	if kv.Exists() == true {
		this.initial = data.New().Decode(kv.Val())
		this.current = data.New().Decode(kv.Val())
	}

	this.id = sql.NewThing(this.key.TB, this.key.ID)

	return this

}

func (this *Doc) getRules() {

	this.rules = make(map[string]*sql.DefineRulesStatement)

	beg := &keys.RU{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, RU: keys.Prefix}
	end := &keys.RU{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, RU: keys.Suffix}
	rng, _ := this.tx.RGet(beg.Encode(), end.Encode(), 0)

	for _, kv := range rng {
		var rul sql.DefineRulesStatement
		key := new(keys.RU)
		key.Decode(kv.Key())
		if str, ok := key.RU.(string); ok {
			pack.Decode(kv.Val(), &rul)
			this.rules[str] = &rul
		}
	}

	return

}

func (this *Doc) getFields() {

	beg := &keys.FD{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, FD: keys.Prefix}
	end := &keys.FD{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, FD: keys.Suffix}
	rng, _ := this.tx.RGet(beg.Encode(), end.Encode(), 0)

	for _, kv := range rng {
		var fld sql.DefineFieldStatement
		pack.Decode(kv.Val(), &fld)
		this.fields = append(this.fields, &fld)
	}

	return

}

func (this *Doc) getIndexs() {

	beg := &keys.IX{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: keys.Prefix}
	end := &keys.IX{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: keys.Suffix}
	rng, _ := this.tx.RGet(beg.Encode(), end.Encode(), 0)

	for _, kv := range rng {
		var idx sql.DefineIndexStatement
		pack.Decode(kv.Val(), &idx)
		this.indexs = append(this.indexs, &idx)
	}

	return

}

func (this *Doc) StartThing() (err error) {

	return this.tx.CPut(this.key.Encode(), this.current.Encode(), nil)

}

func (this *Doc) PurgeThing() (err error) {

	return this.tx.Del(this.key.Encode())

}

func (this *Doc) StoreThing() (err error) {

	return this.tx.CPut(this.key.Encode(), this.current.Encode(), this.kv.Val())

}

func (this *Doc) PurgePatch() (err error) {

	beg := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.StartOfTime}
	end := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.EndOfTime}
	return this.tx.RDel(beg.Encode(), end.Encode(), 0)

}

func (this *Doc) StorePatch() (err error) {

	// key := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID}
	// return this.tx.CPut(key.Encode(), this.diff().Encode(), nil)
	return

}

func (this *Doc) PurgeIndex() (err error) {

	for _, index := range this.indexs {

		old := buildIndex(index.Cols, this.initial)

		if index.Uniq == true {
			for _, o := range old {
				key := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o}
				this.tx.CDel(key.Encode(), this.id.Bytes())
			}
		}

		if index.Uniq == false {
			for _, o := range old {
				key := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o, ID: this.key.ID}
				this.tx.CDel(key.Encode(), this.id.Bytes())
			}
		}

	}

	return

}

func (this *Doc) StoreIndex() (err error) {

	for _, index := range this.indexs {

		old := buildIndex(index.Cols, this.initial)
		now := buildIndex(index.Cols, this.current)

		diffIndex(old, now)

		if index.Uniq == true {
			for _, o := range old {
				oidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o}
				this.tx.CDel(oidx.Encode(), this.id.Bytes())
			}
			for _, n := range now {
				nidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: n}
				if err = this.tx.CPut(nidx.Encode(), this.id.Bytes(), nil); err != nil {
					return fmt.Errorf("Duplicate entry for %v in index '%s' on %s", n, index.Name, this.key.TB)
				}
			}
		}

		if index.Uniq == false {
			for _, o := range old {
				oidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o, ID: this.key.ID}
				this.tx.CDel(oidx.Encode(), this.id.Bytes())
			}
			for _, n := range now {
				nidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: n, ID: this.key.ID}
				if err = this.tx.CPut(nidx.Encode(), this.id.Bytes(), nil); err != nil {
					return fmt.Errorf("Multiple items with id %s in index '%s' on %s", this.key.ID, index.Name, this.key.TB)
				}
			}
		}

	}

	return

}

func diffIndex(old, now [][]interface{}) {

	var d bool

	for i := len(old) - 1; i >= 0; i-- {
		o := old[i]
		for j := len(now) - 1; j >= 0; j-- {
			n := now[j]
			if reflect.DeepEqual(o, n) {
				d = true
				copy(now[j:], now[j+1:])
				now[len(now)-1] = nil
				now = now[:len(now)-1]
			}
		}
		if d {
			d = false
			copy(old[i:], old[i+1:])
			old[len(old)-1] = nil
			old = old[:len(old)-1]
		}
	}

}

func buildIndex(cols []string, item *data.Doc) (out [][]interface{}) {

	if len(cols) == 0 {
		return [][]interface{}{nil}
	}

	col, cols := cols[0], cols[1:]

	sub := buildIndex(cols, item)

	if arr, ok := item.Get("data", col).Data().([]interface{}); ok {
		for _, s := range sub {
			for _, a := range arr {
				idx := []interface{}{}
				idx = append(idx, a)
				idx = append(idx, s...)
				out = append(out, idx)
			}
		}
	} else {
		for _, s := range sub {
			idx := []interface{}{}
			idx = append(idx, item.Get("data", col).Data())
			idx = append(idx, s...)
			out = append(out, idx)
		}
	}

	return

}