package main

import (
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"unsafe"

	"github.com/pocketbase/pocketbase/core"
)

type CacheBrawlsetListItem struct {
	Name 						string
	SanitizedName   string
	IconsSvgUri 		[]string 
}

type CacheCarteListItem struct {
	Name 									string 
	Url 					 				string
	Layout 								string
	SmallImage 						string
	SmallImageBack 				string
	NormalImage 					string
	NormalImageBack 			string
	Price 								float64
	CardmarketUri 				string
	NumberOfDecks 				int 
	NumberOfPossibleDecks int
	PercentageOfDecks 		float64
	Synergy 							float64
}

type CacheCarteListItemWithSynergy struct {
	MainCard 							CacheCarteListItem
	CanBeCommander 				bool
	Cards 								map[string][]CacheCarteListItem
}

type CacheCarteSearch struct {
	Name 									string
	Url 									string
	SetName 							string
}

type CacheBrawlsetData struct {
	Name 														string
	SanitizedName 									string 
	Cards 													map[string][]CacheCarteListItem
}

func (app *application) SetupCache() {
	app.pb.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error {
		if err := e.Next(); err != nil {
			return err
		}

		GenerateCache(e.App)

		return nil
	})
}

func GenerateCache(pb core.App) {
	log.Println("Creating json data cache...")
	defer timer("Generating cache")()
	var totalSize int64

	brawlsets := []Brawlset{}
	mtgSetsQuery := []MtgSet{}
	mtgSets := map[string]MtgSet{}
	cardsQuery := []Carte{}
	cards := map[string]Carte{}
	cacheCardsSearch := []CacheCarteSearch{}
	cardsBySet := map[string][]Carte{}
	decks := []Deck{}
	decksByBset := map[string][]Deck{}

	err := pb.DB().
		NewQuery("SELECT * FROM brawlset").
		All(&brawlsets)
	if err != nil {
		log.Println(fmt.Sprintf("[ERROR] %v", err))	
	}	

	err = pb.DB().
		NewQuery("SELECT * FROM mtg_set").
		All(&mtgSetsQuery)
	if err != nil {
		log.Println(fmt.Sprintf("[ERROR] %v", err))	
	}

	err = pb.DB().
		NewQuery("SELECT * FROM deck").
		All(&decks)
	if err != nil {
		log.Println(fmt.Sprintf("[ERROR] %v", err))	
	}

	for _, v := range decks {
		decksByBset[v.Brawlset] = append(decksByBset[v.Brawlset], v)
	}

	err = pb.DB().
		NewQuery("SELECT * FROM carte").
		All(&cardsQuery)
	if err != nil {
		log.Println(fmt.Sprintf("[ERROR] %v", err))	
	}

	for _, v := range mtgSetsQuery {
		mtgSets[v.ID] = v
	}

	for _, v := range cardsQuery {
		cards[v.ID] = v
		cardsBySet[v.MtgSet] = append(cardsBySet[v.MtgSet], v)
		cacheCardsSearch = append(cacheCardsSearch, CacheCarteSearch{ Name: v.Name, Url: fmt.Sprintf("/card/%s-%s", v.SetCode,v.SanitizedName), SetName: mtgSets[v.MtgSet].Name})
	}

	totalSize += int64(unsafe.Sizeof(cacheCardsSearch))
	pb.Store().Set("searchData", cacheCardsSearch)

	cacheBrawlsetList := []CacheBrawlsetListItem{}
	for _, v := range brawlsets {
		setIconList := []string{}
		numberOfDecksPerColorIdentity := map[string]int{}
		cacheBrawlsetData := CacheBrawlsetData{Name: v.Name, SanitizedName: v.SanitizedName, Cards: map[string][]CacheCarteListItem{}}
		numberOfDecksPerCard := map[string]int{}
		synergyPerCards := map[string]map[string]int{}

		for _, d := range decksByBset[v.ID] {
			
			possibleColorIdentities := GetAllColorCombination(d.ColorIdentity)
			for _, color := range possibleColorIdentities {
				if _, ok := numberOfDecksPerColorIdentity[color]; ok {
					numberOfDecksPerColorIdentity[color]++
				} else {
					numberOfDecksPerColorIdentity[color] += 1
				}
			}
			// Increment number of deck for commander
			if _, ok := numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)]; ok {
				numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)]++
			} else {
				numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)] = 1
			}

			// Increment number of deck for each card
			for _, c := range d.Cards {
				if _, ok := synergyPerCards[fmt.Sprintf("c-%s",d.Commander)]; !ok {
					synergyPerCards[fmt.Sprintf("c-%s", d.Commander)] = map[string]int{}
				}
				if _, ok := synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID]; ok {
					synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID]++
				} else {
					synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID] = 1
				}

				// Add number of decks per card
				if _, ok := numberOfDecksPerCard[c.ID]; ok {
					numberOfDecksPerCard[c.ID]++
				} else {
					numberOfDecksPerCard[c.ID] = 1
				}

				// Add number of decks with the two same cards for synergy
				for _, tc := range d.Cards {
					if _, ok := synergyPerCards[c.ID]; !ok {
						synergyPerCards[c.ID] = map[string]int{}
					}
					
					if _, ok := synergyPerCards[c.ID][tc.ID]; ok {
						synergyPerCards[c.ID][tc.ID]++
					} else {
						synergyPerCards[c.ID][tc.ID] = 1
					}
				}
			}
		}
		
		for _, s := range v.Sets {
			setIconList = append(setIconList, mtgSets[s].IconUri)
			for _, c := range cardsBySet[s] {
				obj := CreateCardData(c, decksByBset, numberOfDecksPerColorIdentity, numberOfDecksPerCard, v.ID)
				cacheBrawlsetData.Cards[c.CardType] = append(cacheBrawlsetData.Cards[c.CardType], obj)

				if c.CanBeCommander {
					obj.NumberOfDecks = numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)]
					obj.Url = fmt.Sprintf("/commander/%s-%s", c.SetCode,c.SanitizedName)
					cacheBrawlsetData.Cards["commander"] = append(cacheBrawlsetData.Cards["commander"], obj)

					detailsObj := CacheCarteListItemWithSynergy{
						MainCard: obj,
						CanBeCommander: true,
						Cards: map[string][]CacheCarteListItem{},
					}

					// Add each card that already appeared in a deck
					for k := range synergyPerCards[fmt.Sprintf("c-%s",c.ID)] {
						synergyObj := CreateCardData(cards[k], decksByBset, numberOfDecksPerColorIdentity, numberOfDecksPerCard, v.ID)

						synergy := 0 - synergyObj.PercentageOfDecks
						if numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)] != 0 {
							synergy = (float64(synergyPerCards[fmt.Sprintf("c-%s",c.ID)][k]) /  float64(numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)])) - synergyObj.PercentageOfDecks
						}
						synergyObj.Synergy = synergy

						detailsObj.Cards[cards[k].CardType] = append(detailsObj.Cards[cards[k].CardType], synergyObj)
					}

					for k := range detailsObj.Cards {
						sort.Slice(detailsObj.Cards[k], func(i, j int) bool {
							if detailsObj.Cards[k][i].Synergy == detailsObj.Cards[k][j].Synergy {
								return detailsObj.Cards[k][i].PercentageOfDecks > detailsObj.Cards[k][j].PercentageOfDecks
							}
							return detailsObj.Cards[k][i].Synergy > detailsObj.Cards[k][j].Synergy
						})	
					}

					totalSize += int64(unsafe.Sizeof(detailsObj))
					pb.Store().Set(fmt.Sprintf("json/commander/%s-%s", c.SetCode, c.SanitizedName), detailsObj)
				}
			}
		}

		for k := range cacheBrawlsetData.Cards {
			if k == "commander" {
				sort.Slice(cacheBrawlsetData.Cards[k], func(i, j int) bool { return cacheBrawlsetData.Cards[k][i].NumberOfDecks > cacheBrawlsetData.Cards[k][j].NumberOfDecks})	
			} else {
				sort.Slice(cacheBrawlsetData.Cards[k], func(i, j int) bool {
					if cacheBrawlsetData.Cards[k][i].PercentageOfDecks == cacheBrawlsetData.Cards[k][j].PercentageOfDecks {
						return cacheBrawlsetData.Cards[k][i].NumberOfDecks > cacheBrawlsetData.Cards[k][j].NumberOfDecks
					}
					return cacheBrawlsetData.Cards[k][i].PercentageOfDecks > cacheBrawlsetData.Cards[k][j].PercentageOfDecks
				})	
			}
		}

		cacheBrawlsetList = append(cacheBrawlsetList, CacheBrawlsetListItem{Name: v.Name, SanitizedName: v.SanitizedName, IconsSvgUri: setIconList})

		totalSize += int64(unsafe.Sizeof(cacheBrawlsetData))
		pb.Store().Set("json/brawlset/" + v.SanitizedName, cacheBrawlsetData)
	}

	totalSize += int64(unsafe.Sizeof(cacheBrawlsetList))
	pb.Store().Set("json/misc/brawlsets", cacheBrawlsetList)

	log.Println(fmt.Sprintf("Total cache size : %d", totalSize))
}

func GetAllColorCombination(s []string) []string {
	res := []string{strings.Join(s,"")}
	for i := 1; i < len(s); i++ {
		combinations := CreateCombination(s, i)
		res = append(res, combinations...)
	}
	return res
}

func CreateCardData(c Carte, decksByBset map[string][]Deck, numberOfDecksPerColorIdentity map[string]int, numberOfDecksPerCard map[string]int, bsetID string) CacheCarteListItem {
	price, err := strconv.ParseFloat(c.Price, 64)
	if err != nil {
		price = 0.0
	}

	numberOfPossibleDecks := 0
	if len(c.ColorIdentity) == 0 {
		numberOfPossibleDecks = len(decksByBset[bsetID])	
	} else {
		numberOfPossibleDecks = numberOfDecksPerColorIdentity[strings.Join(c.ColorIdentity,"")]
	}

	percentageOfDecks := 0.0
	if numberOfPossibleDecks != 0 {
		percentageOfDecks = float64(numberOfDecksPerCard[c.ID]) / float64(numberOfPossibleDecks) 
	}

	return CacheCarteListItem{
					Name: c.Name,
					Url: fmt.Sprintf("/card/%s-%s", c.SetCode,c.SanitizedName),
					Layout: c.Layout,
					SmallImage: c.SmallImage,
					SmallImageBack: c.SmallImageBack,
					NormalImage: c.NormalImage,
					NormalImageBack: c.NormalImageBack,
					Price: price,
					CardmarketUri: c.CardmarketUri,
					NumberOfDecks: numberOfDecksPerCard[c.ID],
					NumberOfPossibleDecks: numberOfPossibleDecks,
					PercentageOfDecks: percentageOfDecks,
				}
}
