package cluster

import (
	"fmt"

	"github.com/pingcap/dbaas/central/db/entity"
)

var (
	_ Differ = (*entityIDSlice)(nil)
	_ Differ = (*intSlice)(nil)
)

type Differ interface {
	Len() int
	GetUniqID(i int) string
}

// Diff compares source to target, return
// * item index should be add in source
// * item index should be delete in target
// NOTE: both items in source and target should be unique
func Diff(source, target Differ) (toAddInSource []int, toDeleteInTarget []int) {
	if source.Len() == 0 {
		// all things in target should be delete
		for i := 0; i < target.Len(); i++ {
			toDeleteInTarget = append(toDeleteInTarget, i)
		}
		return
	}

	if target.Len() == 0 {
		// all things in source should be add
		for i := 0; i < source.Len(); i++ {
			toAddInSource = append(toAddInSource, i)
		}
		return
	}

	sourceMap := make(map[string]bool)
	targetMap := make(map[string]bool)
	for i := 0; i < source.Len(); i++ {
		sourceMap[source.GetUniqID(i)] = true
	}
	for i := 0; i < target.Len(); i++ {
		targetMap[target.GetUniqID(i)] = true
	}

	// find those in target, but not in source
	for i := 0; i < target.Len(); i++ {
		if !sourceMap[target.GetUniqID(i)] {
			toDeleteInTarget = append(toDeleteInTarget, i)
		}
	}
	// find those in source, but not in target
	for i := 0; i < source.Len(); i++ {
		if !targetMap[source.GetUniqID(i)] {
			toAddInSource = append(toAddInSource, i)
		}
	}
	return //nolint
}

type entityIDSlice []entity.ID

func (s entityIDSlice) Len() int {
	return len(s)
}

func (s entityIDSlice) GetUniqID(i int) string {
	return fmt.Sprintf("%d", s[i].Uint64())
}

func DiffEntityID(source, target []entity.ID) (toAdd, toDel []entity.ID) {
	indexesToAdd, indexesToDel := Diff(entityIDSlice(source), entityIDSlice(target))
	for _, i := range indexesToAdd {
		toAdd = append(toAdd, source[i])
	}

	for _, i := range indexesToDel {
		toDel = append(toDel, target[i])
	}
	return
}

type intSlice []int

func (s intSlice) Len() int {
	return len(s)
}

func (s intSlice) GetUniqID(i int) string {
	return fmt.Sprintf("%d", s[i])
}

func DiffInt(source, target []int) (toAdd, toDel []int) {
	indexesToAdd, indexesToDel := Diff(intSlice(source), intSlice(target))
	for _, i := range indexesToAdd {
		toAdd = append(toAdd, source[i])
	}

	for _, i := range indexesToDel {
		toDel = append(toDel, target[i])
	}
	return
}

type stringSlice []string

func (s stringSlice) Len() int {
	return len(s)
}

func (s stringSlice) GetUniqID(i int) string {
	return s[i]
}

func DiffString(source, target []string) (toAdd, toDel []string) {
	indexesToAdd, indexesToDel := Diff(stringSlice(source), stringSlice(target))
	for _, i := range indexesToAdd {
		toAdd = append(toAdd, source[i])
	}

	for _, i := range indexesToDel {
		toDel = append(toDel, target[i])
	}
	return
}

func UniqueStringSlice(s []string) []string {
	stringMap := make(map[string]bool)

	for _, i := range s {
		stringMap[i] = true
	}

	var result []string
	for i := range stringMap {
		result = append(result, i)
	}

	return result
}
