// Copyright 2016 Russ Olsen. All Rights Reserved.
//
// This code is a Go port of the Java version created and maintained by Cognitect, therefore:
//
// Copyright 2014 Cognitect. 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 transit

import (
	"fmt"
)

const cacheCodeDigits = 44
const baseCharIndex = 48
const firstOrd = 48
const cacheSize = (cacheCodeDigits * cacheCodeDigits)
const minSizeCacheable = 4

type StringMap map[string]string

type RollingCache struct {
	keyToValue StringMap
	valueToKey StringMap
}

func NewRollingCache() *RollingCache {
	return &RollingCache{keyToValue: make(StringMap), valueToKey: make(StringMap)}
}

func (rc *RollingCache) String() string {
	return fmt.Sprintf("Cache: %v", rc.keyToValue)
}

func (rc *RollingCache) HasKey(name string) bool {
	_, present := rc.keyToValue[name]
	return present
}

func (rc *RollingCache) Read(name string) string {
	return rc.keyToValue[name]
}

// Enter the name into the cache if it passes the cacheable critieria.
// Returns either the name or the value that was previously cached for
// the name.
func (rc *RollingCache) Write(name string) string {
	existing_key, present := rc.valueToKey[name]

	if present {
		return existing_key
	}

	if rc.isCacheFull() {
		rc.Clear()
	}

	var key = rc.encodeKey(len(rc.keyToValue))
	rc.keyToValue[key] = name
	rc.valueToKey[name] = key

	return name
}

// IsCacheable returns true if the string is long enough to be cached
// and either asKey is true or the string represents a symbol, keyword
// or tag.
func (rc *RollingCache) IsCacheable(s string, asKey bool) bool {
	if len(s) < minSizeCacheable {
		return false
	} else if asKey {
		return true
	} else {
		var firstTwo = s[0:2]
		//return firstTwo == "~#" || firstTwo == "~$" || firstTwo == "~:"
		return firstTwo == startTag || firstTwo == startKW || firstTwo == startSym
	}
}

// IsCacheKey returns true if the string looks like a cache key.
func (rc *RollingCache) IsCacheKey(name string) bool {
	if len(name) == 0 {
		return false
	} else if (name[0:1] == sub) && (name != mapAsArray) {
		return true
	} else {
		return false
	}
}

func (rc *RollingCache) encodeKey(index int) string {
	var hi = index / cacheCodeDigits
	var lo = index % cacheCodeDigits
	if hi == 0 {
		return sub + string(lo+baseCharIndex)
	} else {
		return sub + string(hi+baseCharIndex) + string(lo+baseCharIndex)
	}
}

func (rc *RollingCache) codeToIndex(s string) int {
	var sz = len(s)
	if sz == 2 {
		return int(s[1]) - baseCharIndex
	} else {
		return ((int(s[1]) - baseCharIndex) * cacheCodeDigits) + (int(s[2]) - baseCharIndex)
	}
}

func (rc *RollingCache) isCacheFull() bool {
	return len(rc.keyToValue) >= cacheSize
}

func (rc *RollingCache) Clear() {
	rc.valueToKey = make(StringMap)
	rc.keyToValue = make(StringMap)
}