package engine

import (
	"bytes"
	"encoding/json"
	"io"
	"sort"
	"strconv"
)

// Function taking two Env items returning:
//   -1  if fst should be ordered before snd
//    1  if fst should be ordered after snd
//    0  otherwise
type CmpFunc func(fst, snd *Env) int

type Table struct {
	Data    []*Env
	sortKey string
	cmpFunc CmpFunc
	Chan    chan *Env
}

func NewTable(sortKey string, sizeHint int) *Table {
	return &Table{
		make([]*Env, 0, sizeHint),
		sortKey,
		nil,
		make(chan *Env),
	}
}

func NewTableWithCmpFunc(cmp CmpFunc, sizeHint int) *Table {
	return &Table{
		make([]*Env, 0, sizeHint),
		"",
		cmp,
		make(chan *Env),
	}
}

func (t *Table) SetKey(sortKey string) {
	t.sortKey = sortKey
	t.cmpFunc = nil
}

func (t *Table) SetCmpFunc(cmp CmpFunc) {
	t.sortKey = ""
	t.cmpFunc = cmp
}

func (t *Table) Add(env *Env) {
	t.Data = append(t.Data, env)
}

func (t *Table) Len() int {
	return len(t.Data)
}

func (t *Table) Less(a, b int) bool {
	if t.cmpFunc == nil {
		return t.lessBy(a, b, t.sortKey)
	}
	return t.cmpFunc(t.Data[a], t.Data[b]) < 0
}

func (t *Table) lessBy(a, b int, by string) bool {
	keyA := t.Data[a].Get(by)
	keyB := t.Data[b].Get(by)
	intA, errA := strconv.ParseInt(keyA, 10, 64)
	intB, errB := strconv.ParseInt(keyB, 10, 64)
	if errA == nil && errB == nil {
		return intA < intB
	}
	return keyA < keyB
}

func (t *Table) Swap(a, b int) {
	tmp := t.Data[a]
	t.Data[a] = t.Data[b]
	t.Data[b] = tmp
}

func (t *Table) Sort() {
	sort.Sort(t)
}

func (t *Table) ReverseSort() {
	sort.Sort(sort.Reverse(t))
}

func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) {
	if _, err := dst.Write([]byte{'['}); err != nil {
		return -1, err
	}
	n = 1
	for i, env := range t.Data {
		bytes, err := env.WriteTo(dst)
		if err != nil {
			return -1, err
		}
		n += bytes
		if i != len(t.Data)-1 {
			if _, err := dst.Write([]byte{','}); err != nil {
				return -1, err
			}
			n++
		}
	}
	if _, err := dst.Write([]byte{']'}); err != nil {
		return -1, err
	}
	return n + 1, nil
}

func (t *Table) ToListString() (string, error) {
	buffer := bytes.NewBuffer(nil)
	if _, err := t.WriteListTo(buffer); err != nil {
		return "", err
	}
	return buffer.String(), nil
}

func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
	for _, env := range t.Data {
		bytes, err := env.WriteTo(dst)
		if err != nil {
			return -1, err
		}
		n += bytes
	}
	return n, nil
}

func (t *Table) ReadListFrom(src []byte) (n int64, err error) {
	var array []interface{}

	if err := json.Unmarshal(src, &array); err != nil {
		return -1, err
	}

	for _, item := range array {
		if m, ok := item.(map[string]interface{}); ok {
			env := &Env{}
			for key, value := range m {
				env.SetAuto(key, value)
			}
			t.Add(env)
		}
	}

	return int64(len(src)), nil
}

func (t *Table) ReadFrom(src io.Reader) (n int64, err error) {
	decoder := NewDecoder(src)
	for {
		env, err := decoder.Decode()
		if err == io.EOF {
			return 0, nil
		} else if err != nil {
			return -1, err
		}
		t.Add(env)
	}
}
