package model

import (
	"errors"
	"fmt"
	"go-nkode/hashset"
	"go-nkode/util"
)

type UserInterface struct {
	IdxInterface IdxInterface
	Kp           *KeypadDimension
}

func NewUserInterface(kp *KeypadDimension) (*UserInterface, error) {
	idxInterface := util.IdentityArray(kp.TotalAttrs())
	userInterface := UserInterface{
		IdxInterface: idxInterface,
		Kp:           kp,
	}
	err := userInterface.RandomShuffle()
	if err != nil {
		return nil, err
	}
	return &userInterface, nil
}

func (u *UserInterface) RandomShuffle() error {
	err := u.shuffleKeys()

	keypadView, err := u.InterfaceMatrix()
	if err != nil {
		return err
	}
	setView, err := util.MatrixTranspose(keypadView)
	if err != nil {
		return err
	}

	for idx, set := range setView {
		err := util.FisherYatesShuffle(&set)
		if err != nil {
			return nil
		}
		setView[idx] = set
	}

	keypadView, err = util.MatrixTranspose(setView)
	if err != nil {
		return err
	}
	u.IdxInterface = util.MatrixToList(keypadView)
	return nil
}

func (u *UserInterface) InterfaceMatrix() ([][]int, error) {
	return util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
}

func (u *UserInterface) SetViewMatrix() ([][]int, error) {
	keypadView, err := u.InterfaceMatrix()
	if err != nil {
		return nil, err
	}
	return util.MatrixTranspose(keypadView)
}

func (u *UserInterface) DisperseInterface() error {
	if !u.Kp.IsDispersable() {
		panic("interface is not dispersable")
	}

	err := u.shuffleKeys()
	if err != nil {
		return err
	}
	err = u.randomAttributeRotation()
	if err != nil {
		return err
	}

	return nil
}

func (u *UserInterface) shuffleKeys() error {
	userInterfaceMatrix, err := util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
	if err != nil {
		return err
	}
	err = util.FisherYatesShuffle[[]int](&userInterfaceMatrix)
	if err != nil {
		return err
	}
	u.IdxInterface = util.MatrixToList(userInterfaceMatrix)
	return nil
}

func (u *UserInterface) randomAttributeRotation() error {
	userInterface, err := u.InterfaceMatrix()
	if err != nil {
		return err
	}

	transposeUserInterface, err := util.MatrixTranspose(userInterface)

	attrRotation, err := util.RandomPermutation(len(transposeUserInterface))

	if err != nil {
		return err
	}
	for idx, attrSet := range transposeUserInterface {
		rotation := attrRotation[idx]
		transposeUserInterface[idx] = append(attrSet[rotation:], attrSet[:rotation]...)
	}
	userInterface, err = util.MatrixTranspose(transposeUserInterface)
	if err != nil {
		return err
	}
	u.IdxInterface = util.MatrixToList(userInterface)
	return nil
}

func (u *UserInterface) AttributeAdjacencyGraph() (map[int]hashset.Set[int], error) {
	interfaceKeypad, err := u.InterfaceMatrix()
	if err != nil {
		return nil, err
	}
	graph := make(map[int]hashset.Set[int])

	for _, key := range interfaceKeypad {
		keySet := hashset.NewSetFromSlice(key)
		for _, attr := range key {
			attrAdjacency := keySet.Copy()
			attrAdjacency.Remove(attr)
			graph[attr] = attrAdjacency
		}
	}
	return graph, nil
}

func (u *UserInterface) PartialInterfaceShuffle() error {
	err := u.shuffleKeys()
	if err != nil {
		return err
	}
	numbOfSelectedSets := u.Kp.AttrsPerKey / 2
	if u.Kp.AttrsPerKey&1 == 1 {
		numbOfSelectedSets += util.Choice[int]([]int{0, 1})
	}
	setIdxs, err := util.RandomPermutation(u.Kp.AttrsPerKey)
	if err != nil {
		return err
	}
	selectedSets := hashset.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets])

	keypadSetView, err := u.SetViewMatrix()
	if err != nil {
		return err
	}
	interfaceBySet := make([][]int, u.Kp.AttrsPerKey)
	for idx, attrs := range keypadSetView {
		if selectedSets.Contains(idx) {
			err = util.FisherYatesShuffle[int](&attrs)
			if err != nil {
				return err
			}
		}
		interfaceBySet[idx] = attrs
	}
	keypadView, err := util.MatrixTranspose(interfaceBySet)
	if err != nil {
		return err
	}
	u.IdxInterface = util.MatrixToList(keypadView)
	return nil
}

func (u *UserInterface) GetAttrIdxByKeyNumbSetIdx(setIdx int, keyNumb int) (int, error) {
	if keyNumb < 0 || u.Kp.NumbOfKeys <= keyNumb {
		return -1, errors.New(fmt.Sprintf("keyNumb %d is out of range 0-%d", keyNumb, u.Kp.NumbOfKeys))
	}

	if setIdx < 0 || u.Kp.AttrsPerKey <= setIdx {
		return -1, errors.New(fmt.Sprintf("setIdx %d is out of range 0-%d", setIdx, u.Kp.AttrsPerKey))
	}
	keypadView, err := u.InterfaceMatrix()
	if err != nil {
		return -1, err
	}
	return keypadView[keyNumb][setIdx], nil
}
