package gtid

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
)

func Parse(gtidDesc string) (*Gtid, error) {
	return parse(gtidDesc)
}
func parse(gtidDesc string) (*Gtid, error) {
	var gtid Gtid
	gtid.hashMap = make(map[string]int)
	gtidDesc = strings.TrimSpace(gtidDesc)
	if "" == gtidDesc {
		return &gtid, nil
	}
	return &gtid, gtid.uniform(gtidDesc)
}

func (g *Gtid) uniform(gtidDesc string) error {
	hashMap := make(map[string][]string)
	gtidlists := strings.Split(gtidDesc, ",")
	uuidLists := make([]string, 0, len(gtidlists))
	for _, v := range gtidlists {
		if v == "" {
			continue
		}
		numbers := strings.Split(v, ":")
		if len(numbers) < 2 {
			return fmt.Errorf("invalid format:%v", v)
		}
		uuid, err := g.isValidUUID(numbers[0])
		if err != nil {
			return err
		}
		numbers = numbers[1:]
		existsNums, ok := hashMap[uuid]
		if ok {
			for _, v := range existsNums {
				numbers = append(numbers, v)
			}
			hashMap[uuid] = numbers
			continue
		}
		hashMap[uuid] = numbers
		uuidLists = append(uuidLists, uuid)
	}
	sort.Strings(uuidLists)
	g.uuidNumbers = make([]UUIDNumber, 0, len(uuidLists))
	for k, v := range uuidLists {
		number, err := g.uniformNumber(hashMap[v])
		if err != nil {
			return err
		}
		number, err = g.uniformRange(number)
		if err != nil {
			return err
		}
		g.mu.Lock()
		g.hashMap[v] = k
		g.mu.Unlock()
		g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{
			uuid:      v,
			intervals: number,
		})
	}
	return nil
}

func (g *Gtid) reform() {
	sort.Sort(SortUUID(g.uuidNumbers))
	g.hashMap = make(map[string]int, len(g.uuidNumbers))
	for k, v := range g.uuidNumbers {
		g.uuidNumbers[k].intervals, _ = g.uniformRange(v.intervals)
		g.hashMap[v.uuid] = k
	}

}

func (g *Gtid) uniformRange(rg []uuidRange) ([]uuidRange, error) {
	newRg := make([]uuidRange, 0, len(rg))
	sort.Sort(SortRange(rg))
	var last *uuidRange = nil
	for _, v := range rg {
		if last != nil && v.min <= last.max+1 {
			if last.max <= v.max {
				last.max = v.max
			}
			continue
		}
		newRg = append(newRg, v)
		last = &newRg[len(newRg)-1]
	}
	return newRg, nil
}

func subRange(origin, rg []uuidRange) []uuidRange {
	newRg := make([]uuidRange, 0)
	sort.Sort(SortRange(rg))
	sort.Sort(SortRange(origin))
	for i := 0; i < len(origin); i++ {
		ori := origin[i]
		res := make([]uuidRange, 0)
		shouldAdd := true
		for _, v := range rg {
			if v.min <= ori.min && v.max >= ori.max {
				shouldAdd = false
				break
			}
			if v.max < ori.min {
				continue
			}
			if v.min > ori.max {
				break
			}

			ur1 := uuidRange{
				min: ori.min,
				max: v.min - 1,
			}
			ur2 := uuidRange{
				min: v.max + 1,
				max: ori.max,
			}
			if ur1.max >= ur1.min {
				res = append(res, ur1)
			}
			if ur2.max >= ur2.min {
				res = append(res, ur2)
			}

		}
		if len(res) == 0 && shouldAdd {
			res = append(res, ori)
		}
		newRg = append(newRg, res...)
	}
	return newRg
}

func containRange(origin, rg []uuidRange) bool {
	sort.Sort(SortRange(rg))
	sort.Sort(SortRange(origin))
	for i := 0; i < len(rg); i++ {
		ft := rg[i]
		found := false
		for _, v := range origin {
			if v.min <= ft.min && v.max >= ft.max {
				found = true
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func overlapRange(origin, rg []uuidRange) bool {
	sort.Sort(SortRange(rg))
	sort.Sort(SortRange(origin))
	for i := 0; i < len(origin); i++ {
		ori := origin[i]
		for _, v := range rg {
			if v.min <= ori.min && v.max >= ori.max {
				return true
			}
			if v.max < ori.min {
				continue
			}
			if v.min > ori.max {
				break
			}

			ur1 := uuidRange{
				min: ori.min,
				max: v.min - 1,
			}
			ur2 := uuidRange{
				min: v.max + 1,
				max: ori.max,
			}
			if ur1.max >= ur1.min {
				return true
			}
			if ur2.max >= ur2.min {
				return true
			}

		}
	}
	return false
}

func (g *Gtid) uniformNumber(num []string) ([]uuidRange, error) {
	rg := make([]uuidRange, 0, len(num))
	for _, v := range num {
		ret := uuidRange{}
		if splitPos := strings.Index(v, "-"); -1 != splitPos {
			firstPart := string(v[0:splitPos])
			if i64, err := strconv.ParseUint(firstPart, 10, 64); nil == err {
				ret.min = i64
			} else {
				return rg, fmt.Errorf("invalid number %v", firstPart)
			}
			secondPart := string(v[splitPos+1:])
			if i64, err := strconv.ParseUint(secondPart, 10, 64); nil == err {
				ret.max = i64
			} else {
				return rg, fmt.Errorf("invalid number %v", secondPart)
			}
		} else {
			if i64, err := strconv.ParseUint(v, 10, 64); nil == err {
				ret.min = i64
				ret.max = i64
			} else {
				return rg, fmt.Errorf("invalid number %v", v)
			}
		}
		rg = append(rg, ret)
	}
	return rg, nil
}

func (g *Gtid) isValidUUID(uuid string) (string, error) {
	uuid = strings.TrimSpace(strings.ToLower(strings.Replace(uuid, "-", "", -1)))
	if 32 != len(uuid) {
		return "", errors.New("invalid uuid" + uuid)
	}
	return uuid[0:8] + "-" + uuid[8:12] + "-" + uuid[12:16] + "-" + uuid[16:20] + "-" + uuid[20:], nil
}

func (g *Gtid) String() (ret string) {

	if g.changed {
		g.mu.Lock()
		g.reform()
		g.mu.Unlock()
		g.changed = false
	}
	for _, uuidNumber := range g.uuidNumbers {
		s := uuidNumber.uuid
		for _, interval := range uuidNumber.intervals {
			if interval.min == interval.max {
				s = s + ":" + strconv.FormatUint(interval.min, 10)
			} else {
				s = s + ":" + strconv.FormatUint(interval.min, 10) + "-" + strconv.FormatUint(interval.max, 10)
			}
		}
		if "" != ret {
			ret = ret + ","
		}
		ret = ret + s
	}
	return ret
}

func (g *Gtid) AddGtid(uuid string, number uint64) error {
	if strings.TrimSpace(uuid) == "" {
		return nil
	}
	uuid, err := g.isValidUUID(uuid)
	if err != nil {
		return err
	}
	g.mu.RLock()
	id, ok := g.hashMap[uuid]
	g.mu.RUnlock()
	g.mu.Lock()
	defer g.mu.Unlock()
	if ok {
		tmp := g.uuidNumbers[id].intervals
		if len(tmp) > 0 && tmp[len(tmp)-1].max+1 == number {
			g.uuidNumbers[id].intervals[len(tmp)-1].max = number
		} else {
			g.uuidNumbers[id].intervals = append(g.uuidNumbers[id].intervals, uuidRange{
				min: number,
				max: number,
			})
		}
	} else {
		g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{
			uuid: uuid,
			intervals: []uuidRange{uuidRange{
				min: number,
				max: number,
			}},
		})
		g.hashMap[uuid] = len(g.uuidNumbers) - 1
	}
	if len(g.uuidNumbers[id].intervals) > 5000 {
		g.reform()
	}
	g.changed = true
	return nil
}
func (g *Gtid) Add(gtidDesc string) error {
	return g.add(gtidDesc)
}
func (g *Gtid) add(gtidDesc string) error {
	tmpSlice := make([]UUIDNumber, 0)
	gtidlists := strings.Split(gtidDesc, ",")
	for _, v := range gtidlists {
		numbers := strings.Split(v, ":")
		if len(numbers) < 2 {
			return fmt.Errorf("invalid format:%v", v)
		}
		uuid, err := g.isValidUUID(numbers[0])
		if err != nil {
			return err
		}
		num, err := g.uniformNumber(numbers[1:])
		if err != nil {
			return err
		}
		tmpSlice = append(tmpSlice, UUIDNumber{
			uuid:      uuid,
			intervals: num,
		})
	}
	g.changed = true
	g.mu.Lock()
	defer g.mu.Unlock()
	for _, v := range tmpSlice {
		id, ok := g.hashMap[v.uuid]
		if ok {
			n := g.uuidNumbers[id].intervals
			n = append(n, v.intervals...)
			g.uuidNumbers[id].intervals = n
		} else {
			g.uuidNumbers = append(g.uuidNumbers, v)
			g.hashMap[v.uuid] = len(g.uuidNumbers) - 1
		}

		if len(g.uuidNumbers[id].intervals) > 5000 {
			g.reform()
		}
	}
	return nil
}
func (g *Gtid) Sub(gtidDesc string) error {
	return g.sub(gtidDesc)
}
func (g *Gtid) sub(gtidDesc string) error {
	if strings.TrimSpace(gtidDesc) == "" {
		return nil
	}
	tmpSlice := make([]UUIDNumber, 0)
	gtidlists := strings.Split(gtidDesc, ",")
	for _, v := range gtidlists {
		numbers := strings.Split(v, ":")
		if len(numbers) < 2 {
			return fmt.Errorf("invalid format:%v", v)
		}
		uuid, err := g.isValidUUID(numbers[0])
		if err != nil {
			return err
		}
		num, err := g.uniformNumber(numbers[1:])
		if err != nil {
			return err
		}
		tmpSlice = append(tmpSlice, UUIDNumber{
			uuid:      uuid,
			intervals: num,
		})
	}
	g.changed = true
	g.mu.Lock()
	defer g.mu.Unlock()
	for _, v := range tmpSlice {
		id, ok := g.hashMap[v.uuid]
		if ok {
			n := subRange(g.uuidNumbers[id].intervals, v.intervals)
			n, _ = g.uniformRange(n)
			if len(n) == 0 || (len(n) == 1 && n[0].max == 0) {
				delete(g.hashMap, v.uuid)
				g.uuidNumbers = append(g.uuidNumbers[:id], g.uuidNumbers[id+1:]...)
				for k, v := range g.uuidNumbers {
					g.hashMap[v.uuid] = k
				}
				continue
			} else {
				g.uuidNumbers[id].intervals = n
			}
		} else {
			continue
		}
		if len(g.uuidNumbers[id].intervals) > 5000 {
			g.reform()
		}
	}
	return nil
}

func (g *Gtid) ContainGtid(uuid string, number uint64) (bool, error) {
	if strings.TrimSpace(uuid) == "" {
		return true, nil
	}
	uuid, err := g.isValidUUID(uuid)
	if err != nil {
		return false, err
	}
	g.mu.Lock()
	defer g.mu.Unlock()
	id, ok := g.hashMap[uuid]
	if ok {
		if g.changed {
			g.reform()
		}
		for _, v := range g.uuidNumbers[id].intervals {
			if v.min <= number && v.max >= number {
				return true, nil
			}
		}
	}
	return false, nil
}

func (g *Gtid) Contain(gtidDesc string) (bool, error) {
	return g.contain(gtidDesc)
}

func (g *Gtid) contain(gtidDesc string) (bool, error) {
	if strings.TrimSpace(gtidDesc) == "" {
		return true, nil
	}
	tmpSlice := make([]UUIDNumber, 0)
	gtidlists := strings.Split(gtidDesc, ",")
	for _, v := range gtidlists {
		numbers := strings.Split(v, ":")
		if len(numbers) < 2 {
			return false, fmt.Errorf("invalid format:%v", v)
		}
		uuid, err := g.isValidUUID(numbers[0])
		if err != nil {
			return false, err
		}
		num, err := g.uniformNumber(numbers[1:])
		if err != nil {
			return false, err
		}
		tmpSlice = append(tmpSlice, UUIDNumber{
			uuid:      uuid,
			intervals: num,
		})
	}
	g.mu.Lock()
	defer g.mu.Unlock()
	for _, v := range tmpSlice {
		id, ok := g.hashMap[v.uuid]
		if ok {
			if !containRange(g.uuidNumbers[id].intervals, v.intervals) {
				return false, nil
			}
		} else {
			return false, nil
		}
	}
	return true, nil
}
func (g *Gtid) Overlap(gtidDesc string) (bool, error) {
	return g.overlap(gtidDesc)
}
func (g *Gtid) overlap(gtidDesc string) (bool, error) {
	if strings.TrimSpace(gtidDesc) == "" {
		if g.String() == "" {
			return true, nil
		}
		return false, nil
	}
	tmpSlice := make([]UUIDNumber, 0)
	gtidlists := strings.Split(gtidDesc, ",")
	for _, v := range gtidlists {
		numbers := strings.Split(v, ":")
		if len(numbers) < 2 {
			return false, fmt.Errorf("invalid format:%v", v)
		}
		uuid, err := g.isValidUUID(numbers[0])
		if err != nil {
			return false, err
		}
		num, err := g.uniformNumber(numbers[1:])
		if err != nil {
			return false, err
		}
		tmpSlice = append(tmpSlice, UUIDNumber{
			uuid:      uuid,
			intervals: num,
		})
	}
	g.mu.Lock()
	defer g.mu.Unlock()
	for _, v := range tmpSlice {
		id, ok := g.hashMap[v.uuid]
		if ok {
			if overlapRange(g.uuidNumbers[id].intervals, v.intervals) {
				return true, nil
			}
		} else {
			continue
		}
	}
	return false, nil
}

func (g *Gtid) Equal(gtidDesc string) (bool, error) {
	if g.String() == "" && strings.TrimSpace(gtidDesc) == "" {
		return true, nil
	}

	equ, err := parse(gtidDesc)
	if err != nil {
		return false, err
	}
	return g.String() == equ.String(), nil
}

func (g *Gtid) Clone() *Gtid {
	g.mu.Lock()
	defer g.mu.Unlock()
	var newGtid = &Gtid{}
	newGtid.uuidNumbers = make([]UUIDNumber, len(g.uuidNumbers))
	copy(newGtid.uuidNumbers, g.uuidNumbers)
	newGtid.hashMap = make(map[string]int, len(g.hashMap))
	for k, v := range g.hashMap {
		newGtid.hashMap[k] = v
	}
	if g.changed {
		newGtid.reform()
	}
	return newGtid
}

func (g *Gtid) EventCount() uint64 {
	g.mu.Lock()
	defer g.mu.Unlock()
	if g.changed {
		g.reform()
	}
	var ret uint64 = 0
	for _, uuidNumber := range g.uuidNumbers {
		for _, interval := range uuidNumber.intervals {
			ret += interval.max - interval.min + 1
		}
	}
	return ret
}

func (g *Gtid) EventList() []string {
	g.mu.Lock()
	defer g.mu.Unlock()
	if g.changed {
		g.reform()
	}
	eventList := []string{}
	for _, uuidNumber := range g.uuidNumbers {
		for _, interval := range uuidNumber.intervals {
			for i := interval.min; i <= interval.max; i++ {
				eventList = append(eventList, fmt.Sprintf("%s:%d", uuidNumber.uuid, i))
			}
		}
	}
	return eventList
}
