package binary

// Serialize tags to a array of interleaved key and value strings.
// Common tags like building=yes are serialized to a single unicode
// char to save a few bytes. Common keys to a single ASCII ctrl char.

// Common tags are encoded as a single unicode char from the Unicode
// Private Use Area http://en.wikipedia.org/wiki/Private_Use_(Unicode)
// between U+E000 and U+F8FF. They take three bytes in UTF-8 encoding.
//
// For example: building=yes will need 4 bytes instead of 13 bytes.
// (building=8 + yes=3 + 2x1 for string length, vs. 3+1)

// The most common tag kays with variable values (name, addr:street,
// etc.) are converted to a single ASCII control char (0x01-0x1f)

import (
	"github.com/omniscale/imposm3/element"
	"unicode/utf8"
)

type codepoint rune
type tag struct {
	Key   string
	Value string
}

var tagsToCodePoint = map[string]map[string]codepoint{}
var codePointToTag = map[codepoint]tag{}

var commonKeys = map[string]codepoint{}
var codePointToCommonKey = map[uint8]string{}
var nextKeyCodePoint = codepoint(1)
var maxKeyCodePoint = codepoint(31)

const minCodePoint = codepoint('\uE000')
const maxCodePoint = codepoint('\uF8FF')

var nextCodePoint = codepoint('\uE000')

func addTagCodePoint(key, value string) {
	if nextCodePoint > maxCodePoint {
		panic("all codepoints used!")
	}
	valMap, ok := tagsToCodePoint[key]
	if !ok {
		tagsToCodePoint[key] = map[string]codepoint{value: nextCodePoint}
	} else {
		if _, ok := valMap[value]; ok {
			panic("duplicate entry for tag codepoints: " + key + " " + value)
		}
		valMap[value] = nextCodePoint
	}

	codePointToTag[nextCodePoint] = tag{key, value}
	nextCodePoint += 1
}

func addCommonKey(key string) {
	if nextKeyCodePoint > maxKeyCodePoint {
		panic("all codepoints used!")
	}
	commonKeys[key] = nextKeyCodePoint
	codePointToCommonKey[uint8(nextKeyCodePoint)] = key
	nextKeyCodePoint += 1
}

func tagsFromArray(arr []string) element.Tags {
	if len(arr) == 0 {
		return element.Tags{}
	}
	result := make(element.Tags)
	for i := 0; i < len(arr); i += 1 {
		if r, size := utf8.DecodeRuneInString(arr[i]); size >= 3 &&
			codepoint(r) >= minCodePoint &&
			codepoint(r) < nextCodePoint {
			tag, ok := codePointToTag[codepoint(r)]
			if !ok {
				panic("missing tag for codepoint")
			}
			result[tag.Key] = tag.Value
		} else if len(arr[i]) > 0 && arr[i][0] < 32 {
			result[codePointToCommonKey[arr[i][0]]] = arr[i][1:]
		} else {
			result[arr[i]] = arr[i+1]
			i++
		}
	}
	return result
}

func tagsAsArray(tags element.Tags) []string {
	if len(tags) == 0 {
		return nil
	}
	result := make([]string, 0, 2*len(tags))
	for key, val := range tags {
		if valMap, ok := tagsToCodePoint[key]; ok {
			if codePoint, ok := valMap[val]; ok {
				result = append(result, string(codePoint))
				continue
			}
		}
		if codepoint, ok := commonKeys[key]; ok {
			result = append(result, string(codepoint)+val)
			continue
		}
		result = append(result, key, val)
	}
	return result
}

func init() {
	//
	// DO NOT EDIT, REMOVE, REORDER ANY OF THE FOLLOWING LINES!
	//

	// most common keys with variable values
	// there are only 31 code points for common keys
	addCommonKey("name")
	addCommonKey("addr:street")
	addCommonKey("addr:place")
	addCommonKey("addr:city")
	addCommonKey("addr:postcode")
	addCommonKey("addr:housenumber")

	// most used tags for ways
	//
	addTagCodePoint("building", "yes")
	addTagCodePoint("highway", "residential")
	addTagCodePoint("highway", "service")
	addTagCodePoint("wall", "no")
	addTagCodePoint("highway", "unclassified")
	addTagCodePoint("waterway", "stream")
	addTagCodePoint("highway", "track")
	addTagCodePoint("natural", "water")
	addTagCodePoint("oneway", "yes")
	addTagCodePoint("highway", "footway")
	addTagCodePoint("highway", "tertiary")
	addTagCodePoint("access", "private")
	addTagCodePoint("highway", "path")
	addTagCodePoint("highway", "secondary")
	addTagCodePoint("landuse", "forest")
	addTagCodePoint("building", "house")
	addTagCodePoint("bridge", "yes")
	addTagCodePoint("surface", "asphalt")
	addTagCodePoint("natural", "wood")
	addTagCodePoint("foot", "yes")
	addTagCodePoint("landuse", "residential")
	addTagCodePoint("surface", "paved")
	addTagCodePoint("highway", "primary")
	addTagCodePoint("surface", "unpaved")
	addTagCodePoint("landuse", "grass")
	addTagCodePoint("building", "residential")
	addTagCodePoint("service", "parking_aisle")
	addTagCodePoint("oneway", "no")
	addTagCodePoint("railway", "rail")
	addTagCodePoint("bicycle", "yes")
	addTagCodePoint("service", "driveway")
	addTagCodePoint("amenity", "parking")
	addTagCodePoint("area", "yes")
	addTagCodePoint("barrier", "fence")
	addTagCodePoint("tracktype", "grade2")
	addTagCodePoint("natural", "coastline")
	addTagCodePoint("tracktype", "grade3")
	addTagCodePoint("intermittent", "yes")
	addTagCodePoint("landuse", "farmland")
	addTagCodePoint("building", "hut")
	addTagCodePoint("boundary", "administrative")
	addTagCodePoint("lit", "yes")
	addTagCodePoint("highway", "cycleway")
	addTagCodePoint("landuse", "meadow")
	addTagCodePoint("waterway", "river")
	addTagCodePoint("natural", "wetland")
	addTagCodePoint("highway", "trunk")
	addTagCodePoint("surface", "gravel")
	addTagCodePoint("tracktype", "grade1")
	addTagCodePoint("barrier", "wall")
	addTagCodePoint("building", "garage")
	addTagCodePoint("highway", "living_street")
	addTagCodePoint("highway", "motorway")
	addTagCodePoint("tracktype", "grade4")
	addTagCodePoint("landuse", "farm")
	addTagCodePoint("leisure", "pitch")
	addTagCodePoint("surface", "ground")
	addTagCodePoint("tunnel", "yes")
	addTagCodePoint("highway", "motorway_link")
	addTagCodePoint("bicycle", "no")
	addTagCodePoint("highway", "road")
	addTagCodePoint("natural", "scrub")
	addTagCodePoint("highway", "steps")
	addTagCodePoint("foot", "designated")
	addTagCodePoint("waterway", "ditch")
	addTagCodePoint("admin_level", "8")
	addTagCodePoint("tracktype", "grade5")
	addTagCodePoint("access", "yes")
	addTagCodePoint("building", "apartments")
	addTagCodePoint("leisure", "swimming_pool")
	addTagCodePoint("junction", "roundabout")
	addTagCodePoint("highway", "pedestrian")
	addTagCodePoint("barrier", "hedge")
	addTagCodePoint("bicycle", "designated")
	addTagCodePoint("leisure", "park")
	addTagCodePoint("service", "alley")
	addTagCodePoint("landuse", "farmyard")
	addTagCodePoint("building", "industrial")
	addTagCodePoint("waterway", "riverbank")
	addTagCodePoint("building", "roof")
	addTagCodePoint("surface", "dirt")
	addTagCodePoint("waterway", "drain")
	addTagCodePoint("surface", "grass")
	addTagCodePoint("amenity", "school")
	addTagCodePoint("power", "line")
	addTagCodePoint("landuse", "industrial")
	addTagCodePoint("landuse", "reservoir")
	addTagCodePoint("water", "intermittent")
	addTagCodePoint("highway", "trunk_link")
	addTagCodePoint("segregated", "no")
	addTagCodePoint("horse", "no")
	addTagCodePoint("wood", "deciduous")
	addTagCodePoint("highway", "primary_link")
	addTagCodePoint("foot", "no")
	addTagCodePoint("lit", "no")
	addTagCodePoint("surface", "concrete")
	addTagCodePoint("building", "garages")
	addTagCodePoint("amenity", "place_of_worship")
	addTagCodePoint("religion", "christian")
	addTagCodePoint("waterway", "canal")
	addTagCodePoint("landuse", "orchard")
	addTagCodePoint("surface", "paving_stones")
	addTagCodePoint("leisure", "garden")
	addTagCodePoint("service", "spur")
	addTagCodePoint("living_street", "yes")
	addTagCodePoint("access", "permissive")
	addTagCodePoint("sport", "soccer")
	addTagCodePoint("frequency", "0")
	addTagCodePoint("landuse", "cemetery")
	addTagCodePoint("wood", "mixed")
	addTagCodePoint("motorcar", "no")
	addTagCodePoint("access", "no")
	addTagCodePoint("man_made", "pier")
	addTagCodePoint("oneway", "-1")
	addTagCodePoint("sport", "tennis")
	addTagCodePoint("noexit", "yes")
	addTagCodePoint("service", "yard")
	addTagCodePoint("wood", "coniferous")
	addTagCodePoint("natural", "cliff")
	addTagCodePoint("leisure", "playground")
	addTagCodePoint("cycleway", "lane")
	addTagCodePoint("surface", "cobblestone")
	addTagCodePoint("landuse", "vineyard")
	addTagCodePoint("frequency", "16.7")
	//
	// most used tags for nodes
	//
	addTagCodePoint("power", "tower")
	addTagCodePoint("natural", "tree")
	addTagCodePoint("highway", "bus_stop")
	addTagCodePoint("power", "pole")
	addTagCodePoint("place", "locality")
	addTagCodePoint("highway", "turning_circle")
	addTagCodePoint("highway", "crossing")
	addTagCodePoint("place", "village")
	addTagCodePoint("place", "hamlet")
	addTagCodePoint("highway", "traffic_signals")
	addTagCodePoint("barrier", "gate")
	// addTagCodePoint("admin_level", "8")
	// addTagCodePoint("amenity", "place_of_worship")
	// addTagCodePoint("amenity", "school")
	// addTagCodePoint("religion", "christian")
	addTagCodePoint("amenity", "bench")
	addTagCodePoint("man_made", "survey_point")
	addTagCodePoint("amenity", "restaurant")
	// addTagCodePoint("amenity", "parking")
	addTagCodePoint("natural", "peak")
	addTagCodePoint("railway", "level_crossing")
	addTagCodePoint("type", "broad_leaved")
	// addTagCodePoint("building", "yes")
	// addTagCodePoint("foot", "yes")
	// addTagCodePoint("bicycle", "yes")
	addTagCodePoint("highway", "street_lamp")
	addTagCodePoint("tourism", "information")
	addTagCodePoint("wheelchair", "yes")
	addTagCodePoint("building", "entrance")
	addTagCodePoint("public_transport", "stop_position")
	addTagCodePoint("amenity", "fuel")
	// addTagCodePoint("noexit", "yes")
	addTagCodePoint("barrier", "bollard")
	addTagCodePoint("amenity", "post_box")
	addTagCodePoint("natural", "rock")
	// addTagCodePoint("landuse", "forest")
	addTagCodePoint("shelter", "yes")
	addTagCodePoint("emergency", "fire_hydrant")
	addTagCodePoint("public_transport", "platform")
	addTagCodePoint("amenity", "grave_yard")
	addTagCodePoint("shop", "convenience")
	addTagCodePoint("power", "generator")
	addTagCodePoint("shop", "supermarket")
	addTagCodePoint("amenity", "bank")
	addTagCodePoint("amenity", "fast_food")
	addTagCodePoint("amenity", "cafe")
	//
	// most used tags for rels
	//
	addTagCodePoint("type", "multipolygon")
	addTagCodePoint("type", "route")
	// addTagCodePoint("boundary", "administrative")
	addTagCodePoint("type", "restriction")
	addTagCodePoint("type", "boundary")
	addTagCodePoint("type", "site")
	// addTagCodePoint("admin_level", "8")
	addTagCodePoint("type", "associatedStreet")
	// addTagCodePoint("natural", "water")
}
