package orderedmap

// mostly from https://github.com/iancoleman/orderedmap

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/lbryio/lbry.go/extras/errors"
)

type keyIndex struct {
	Key   string
	Index int
}

type byIndex []keyIndex

func (a byIndex) Len() int           { return len(a) }
func (a byIndex) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byIndex) Less(i, j int) bool { return a[i].Index < a[j].Index }

type Map struct {
	l      sync.RWMutex
	keys   []string
	values map[string]interface{}
}

func New() *Map {
	o := Map{}
	o.l = sync.RWMutex{}
	o.keys = []string{}
	o.values = map[string]interface{}{}
	return &o
}

func (o *Map) Get(key string) (interface{}, bool) {
	o.l.RLock()
	defer o.l.RUnlock()
	val, exists := o.values[key]
	return val, exists
}

func (o *Map) Set(key string, value interface{}) {
	o.l.Lock()
	defer o.l.Unlock()
	_, exists := o.values[key]
	if !exists {
		o.keys = append(o.keys, key)
	}
	o.values[key] = value
}

const outOfRange = "position value %d is outside of the range %d - %d"

//InsertAt This is a zero based position index: 0,1,2,3..n(from left) OR -1,-2,-3...-n(from right) where -1 is the last place.
func (o *Map) InsertAt(key string, value interface{}, position int) error {
	o.l.Lock()
	defer o.l.Unlock()
	var index = position
	if position < 0 {
		// support indexing from the back: -1 = last in array.
		index += len(o.keys) + 1
		if index < 0 || index > len(o.keys) {
			return errors.Err(fmt.Sprintf(outOfRange, position, len(o.keys), len(o.keys)-1))
		}
	} else if index > len(o.keys) {
		return errors.Err(fmt.Sprintf(outOfRange, position, len(o.keys), len(o.keys)-1))
	}
	_, exists := o.values[key]
	if !exists {
		// left + key + right
		o.keys = append(o.keys[0:index], append([]string{key}, o.keys[index:]...)...)
	}
	o.values[key] = value

	return nil
}

func (o *Map) Prepend(key string, value interface{}) {
	o.l.Lock()
	defer o.l.Unlock()
	_, exists := o.values[key]
	if !exists {
		o.keys = append([]string{key}, o.keys...)
	}
	o.values[key] = value
}

func (o *Map) Delete(key string) {
	o.l.Lock()
	defer o.l.Unlock()
	// check key is in use
	_, ok := o.values[key]
	if !ok {
		return
	}
	// remove from keys
	for i, k := range o.keys {
		if k == key {
			o.keys = append(o.keys[:i], o.keys[i+1:]...)
			break
		}
	}
	// remove from values
	delete(o.values, key)
}

func (o *Map) Keys() []string {
	o.l.RLock()
	defer o.l.RUnlock()
	return o.keys
}

func (o *Map) UnmarshalJSON(b []byte) error {
	o.l.Lock()
	defer o.l.Unlock()
	m := map[string]interface{}{}
	if err := json.Unmarshal(b, &m); err != nil {
		return errors.Err(err)
	}
	s := string(b)
	mapToOrderedMap(o, s, m)
	return nil
}

func mapToOrderedMap(o *Map, s string, m map[string]interface{}) {
	// Get the order of the keys
	orderedKeys := []keyIndex{}
	for k := range m {
		kEscaped := strings.Replace(k, `"`, `\"`, -1)
		kQuoted := `"` + kEscaped + `"`
		// Find how much content exists before this key.
		// If all content from this key and after is replaced with a close
		// brace, it should still form a valid json string.
		sTrimmed := s
		for len(sTrimmed) > 0 {
			lastIndex := strings.LastIndex(sTrimmed, kQuoted)
			if lastIndex == -1 {
				break
			}
			sTrimmed = sTrimmed[0:lastIndex]
			sTrimmed = strings.TrimSpace(sTrimmed)
			if len(sTrimmed) > 0 && sTrimmed[len(sTrimmed)-1] == ',' {
				sTrimmed = sTrimmed[0 : len(sTrimmed)-1]
			}
			maybeValidJson := sTrimmed + "}"
			testMap := map[string]interface{}{}
			err := json.Unmarshal([]byte(maybeValidJson), &testMap)
			if err == nil {
				// record the position of this key in s
				ki := keyIndex{
					Key:   k,
					Index: len(sTrimmed),
				}
				orderedKeys = append(orderedKeys, ki)
				// shorten the string to get the next key
				startOfValueIndex := lastIndex + len(kQuoted)
				valueStr := s[startOfValueIndex : len(s)-1]
				valueStr = strings.TrimSpace(valueStr)
				if len(valueStr) > 0 && valueStr[0] == ':' {
					valueStr = valueStr[1:]
				}
				valueStr = strings.TrimSpace(valueStr)
				if valueStr[0] == '{' {
					// if the value for this key is a map, convert it to an orderedmap.
					// find end of valueStr by removing everything after last }
					// until it forms valid json
					hasValidJson := false
					i := 1
					for i < len(valueStr) && !hasValidJson {
						if valueStr[i] != '}' {
							i = i + 1
							continue
						}
						subTestMap := map[string]interface{}{}
						testValue := valueStr[0 : i+1]
						err = json.Unmarshal([]byte(testValue), &subTestMap)
						if err == nil {
							hasValidJson = true
							valueStr = testValue
							break
						}
						i = i + 1
					}
					// convert to orderedmap
					if hasValidJson {
						mkTyped := m[k].(map[string]interface{})
						oo := &Map{}
						mapToOrderedMap(oo, valueStr, mkTyped)
						m[k] = oo
					}
				} else if valueStr[0] == '[' {
					// if the value for this key is a []interface, convert any map items to an orderedmap.
					// find end of valueStr by removing everything after last ]
					// until it forms valid json
					hasValidJson := false
					i := 1
					for i < len(valueStr) && !hasValidJson {
						if valueStr[i] != ']' {
							i = i + 1
							continue
						}
						subTestSlice := []interface{}{}
						testValue := valueStr[0 : i+1]
						err = json.Unmarshal([]byte(testValue), &subTestSlice)
						if err == nil {
							hasValidJson = true
							valueStr = testValue
							break
						}
						i = i + 1
					}
					if hasValidJson {
						itemsStr := valueStr[1 : len(valueStr)-1]
						// get next item in the slice
						itemIndex := 0
						startItem := 0
						endItem := 0
						for endItem < len(itemsStr) {
							if itemsStr[endItem] != ',' && endItem < len(itemsStr)-1 {
								endItem = endItem + 1
								continue
							}
							// if this substring compiles to json, it's the next item
							possibleItemStr := strings.TrimSpace(itemsStr[startItem:endItem])
							var possibleItem interface{}
							err = json.Unmarshal([]byte(possibleItemStr), &possibleItem)
							if err != nil {
								endItem = endItem + 1
								continue
							}
							// if item is map, convert to orderedmap
							if possibleItemStr[0] == '{' {
								mkTyped := m[k].([]interface{})
								mkiTyped := mkTyped[itemIndex].(map[string]interface{})
								oo := &Map{}
								mapToOrderedMap(oo, possibleItemStr, mkiTyped)
								// replace original map with orderedmap
								mkTyped[itemIndex] = oo
								m[k] = mkTyped
							}
							// remove this item from itemsStr
							startItem = endItem + 1
							endItem = endItem + 1
							itemIndex = itemIndex + 1
						}
					}
				}
				break
			}
		}
	}
	// Sort the keys
	sort.Sort(byIndex(orderedKeys))
	// Convert sorted keys to string slice
	k := []string{}
	for _, ki := range orderedKeys {
		k = append(k, ki.Key)
	}
	// Set the Map values
	o.values = m
	o.keys = k
}

func (o *Map) Copy() *Map {
	new := New()

	for _, k := range o.keys {
		v, _ := o.Get(k)
		new.Set(k, v)
	}

	return new
}

func (o *Map) MarshalJSON() ([]byte, error) {
	o.l.RLock()
	defer o.l.RUnlock()
	s := "{"
	for _, k := range o.keys {
		// add key
		kEscaped := strings.Replace(k, `"`, `\"`, -1)
		s = s + `"` + kEscaped + `":`
		// add value
		v := o.values[k]
		vBytes, err := json.Marshal(v)
		if err != nil {
			return []byte{}, errors.Err(err)
		}
		s = s + string(vBytes) + ","
	}
	if len(o.keys) > 0 {
		s = s[0 : len(s)-1]
	}
	s = s + "}"
	return []byte(s), nil
}