package mapping

import (
	"path"
	"strings"

	"github.com/omniscale/imposm3/element"
	"github.com/omniscale/imposm3/mapping/config"
)

type TagFilterer interface {
	Filter(tags *element.Tags)
}

func (m *Mapping) NodeTagFilter() TagFilterer {
	if m.Conf.Tags.LoadAll {
		return newExcludeFilter(m.Conf.Tags.Exclude)
	}
	mappings := make(TagTableMapping)
	m.mappings(PointTable, mappings)
	tags := make(map[Key]bool)
	m.extraTags(PointTable, tags)
	m.extraTags(RelationMemberTable, tags)
	return &tagFilter{mappings.asTagMap(), tags}
}

func (m *Mapping) WayTagFilter() TagFilterer {
	if m.Conf.Tags.LoadAll {
		return newExcludeFilter(m.Conf.Tags.Exclude)
	}
	mappings := make(TagTableMapping)
	m.mappings(LineStringTable, mappings)
	m.mappings(PolygonTable, mappings)
	tags := make(map[Key]bool)
	m.extraTags(LineStringTable, tags)
	m.extraTags(PolygonTable, tags)
	m.extraTags(RelationMemberTable, tags)
	return &tagFilter{mappings.asTagMap(), tags}
}

func (m *Mapping) RelationTagFilter() TagFilterer {
	if m.Conf.Tags.LoadAll {
		return newExcludeFilter(m.Conf.Tags.Exclude)
	}
	mappings := make(TagTableMapping)
	// do not filter out type tag for common relations
	mappings["type"] = map[Value][]orderedDestTable{
		"multipolygon": []orderedDestTable{},
		"boundary":     []orderedDestTable{},
		"land_area":    []orderedDestTable{},
	}
	m.mappings(LineStringTable, mappings)
	m.mappings(PolygonTable, mappings)
	m.mappings(RelationTable, mappings)
	m.mappings(RelationMemberTable, mappings)
	tags := make(map[Key]bool)
	m.extraTags(LineStringTable, tags)
	m.extraTags(PolygonTable, tags)
	m.extraTags(RelationTable, tags)
	m.extraTags(RelationMemberTable, tags)
	return &tagFilter{mappings.asTagMap(), tags}
}

type tagMap map[Key]map[Value]struct{}

type tagFilter struct {
	mappings  tagMap
	extraTags map[Key]bool
}

func (f *tagFilter) Filter(tags *element.Tags) {
	if tags == nil {
		return
	}
	for k, v := range *tags {
		values, ok := f.mappings[Key(k)]
		if ok {
			if _, ok := values["__any__"]; ok {
				continue
			} else if _, ok := values[Value(v)]; ok {
				continue
			} else if _, ok := f.extraTags[Key(k)]; !ok {
				delete(*tags, k)
			}
		} else if _, ok := f.extraTags[Key(k)]; !ok {
			delete(*tags, k)
		}
	}
}

type excludeFilter struct {
	keys    map[Key]struct{}
	matches []string
}

func newExcludeFilter(tags []config.Key) *excludeFilter {
	f := excludeFilter{
		keys:    make(map[Key]struct{}),
		matches: make([]string, 0),
	}
	for _, t := range tags {
		if strings.ContainsAny(string(t), "?*[") {
			f.matches = append(f.matches, string(t))
		} else {
			f.keys[Key(t)] = struct{}{}
		}
	}
	return &f
}

func (f *excludeFilter) Filter(tags *element.Tags) {
	for k := range *tags {
		if _, ok := f.keys[Key(k)]; ok {
			delete(*tags, k)
		} else if f.matches != nil {
			for _, exkey := range f.matches {
				if ok, _ := path.Match(exkey, k); ok {
					delete(*tags, k)
					break
				}
			}
		}
	}
}
