package sync

import (
	"fmt"
	"path"
	"strconv"

	"github.com/BurntSushi/toml"
	"go.uber.org/multierr"

	"srs.epita.fr/fic-server/libfic"
)

// ExerciceHintParams holds EHint definition infomation.
type ExerciceHintParams struct {
	Filename string
	Content  string
	Cost     *int64
	Title    string
	NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
}

// ExerciceDependency holds dependency definitions information.
type ExerciceDependency struct {
	Id    int64
	Theme string `toml:",omitempty"`
}

// ExerciceUnlockFile holds parameters related to a locked file.
type ExerciceUnlockFile struct {
	Filename string `toml:",omitempty"`
}

// ExerciceFile defines attributes on files.
type ExerciceFile struct {
	Filename   string `toml:",omitempty"`
	URL        string `toml:",omitempty"`
	Hidden     bool   `toml:",omitempty"`
	Disclaimer string `toml:",omitempty"`
}

// ExerciceFlag holds informations about one flag.
type ExerciceFlag struct {
	Id            int64
	Label         string `toml:",omitempty"`
	Type          string `toml:",omitempty"`
	Raw           interface{}
	Separator     string `toml:",omitempty"`
	ShowLines     bool   `toml:",omitempty"`
	Ordered       bool   `toml:",omitempty"`
	CaseSensitive bool   `toml:",omitempty"`
	NoTrim        bool   `toml:",omitempty"`
	CaptureRe     string `toml:"capture_regexp,omitempty"`
	SortReGroups  bool   `toml:"sort_capture_regexp_groups,omitempty"`
	Placeholder   string `toml:",omitempty"`
	Help          string `toml:",omitempty"`
	BonusGain     int32  `toml:"bonus_gain,omitempty"`
	ChoicesCost   int32  `toml:"choices_cost,omitempty"`
	Choice        []ExerciceFlagChoice
	LockedFile    []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
	NeedFlag      []ExerciceDependency `toml:"need_flag,omitempty"`
	NeedFlags     []int64              `toml:"need_flags,omitempty"`
	NoShuffle     bool
	Unit          string      `toml:"unit,omitempty"`
	Variant       string      `toml:"variant,omitempty"`
	NumberMin     interface{} `toml:"min,omitempty"`
	NumberMax     interface{} `toml:"max,omitempty"`
	NumberStep    interface{} `toml:"step,omitempty"`
}

func (f ExerciceFlag) RawString() []string {
	switch f.Raw.(type) {
	case string:
		return []string{f.Raw.(string)}
	case []string:
		return f.Raw.([]string)
	default:
		return nil
	}
}

func (f ExerciceFlag) RawNumber() ([]float64, error) {
	switch f.Raw.(type) {
	case float64:
		return []float64{f.Raw.(float64)}, nil
	case []float64:
		return f.Raw.([]float64), nil
	case int64:
		return []float64{float64(f.Raw.(int64))}, nil
	case []int64:
		var res []float64
		for _, raw := range f.Raw.([]int64) {
			res = append(res, float64(raw))
		}
		return res, nil
	case string:
		if v, err := strconv.ParseFloat(f.Raw.(string), 64); err == nil {
			return []float64{v}, nil
		} else {
			return nil, err
		}
	case []string:
		var res []float64
		for _, raw := range f.Raw.([]string) {
			if v, err := strconv.ParseFloat(raw, 64); err == nil {
				res = append(res, v)
			} else {
				return nil, err
			}
		}
		return res, nil
	default:
		return nil, fmt.Errorf("invalid raw type: %T", f.Raw)
	}
}

// ExerciceFlagChoice holds informations about a choice (for MCQ and UCQ).
type ExerciceFlagChoice struct {
	ExerciceFlag
	Value interface{} `toml:",omitempty"`
}

// ExerciceParams contains values parsed from defines.txt.
type ExerciceParams struct {
	WIP          bool `toml:"wip"`
	Gain         int64
	Tags         []string
	Files        []ExerciceFile       `toml:"file"`
	Hints        []ExerciceHintParams `toml:"hint"`
	Dependencies []ExerciceDependency `toml:"depend"`
	Flags        []ExerciceFlag       `toml:"flag"`
	FlagsMCQ     []ExerciceFlag       `toml:"flag_mcq"`
	FlagsUCQ     []ExerciceFlag       `toml:"flag_ucq"`
}

func (p ExerciceParams) GetRawFlags() (ret []string) {
	for _, f := range append(p.Flags, p.FlagsUCQ...) {
		raw := f.RawString()
		if len(raw) > 0 {
			ret = append(ret, raw...)
		}
	}
	for _, f := range p.FlagsMCQ {
		for _, c := range f.Choice {
			ret = append(ret, c.Label)
		}
	}
	return
}

// parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge.
func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.MetaData, err error) {
	var defs string

	if i.Exists(path.Join(exPath, "challenge.toml")) {
		defs, err = GetFileContent(i, path.Join(exPath, "challenge.toml"))
	} else {
		defs, err = GetFileContent(i, path.Join(exPath, "challenge.txt"))
	}

	if err != nil {
		return
	}

	md, err = toml.Decode(defs, &p)

	return
}

// getExerciceParams returns normalized
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs error) {
	var err error
	if params, _, err = parseExerciceParams(i, exercice.Path); err != nil {
		errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
	} else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
		errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
	} else {
		// Treat legacy UCQ flags as ExerciceFlag
		for _, flag := range params.FlagsUCQ {
			params.Flags = append(params.Flags, ExerciceFlag{
				Id:          flag.Id,
				Label:       flag.Label,
				Type:        "ucq",
				Raw:         flag.Raw,
				CaptureRe:   flag.CaptureRe,
				Placeholder: flag.Placeholder,
				ChoicesCost: flag.ChoicesCost,
				Choice:      flag.Choice,
				LockedFile:  flag.LockedFile,
				NeedFlag:    flag.NeedFlag,
			})
		}
		params.FlagsUCQ = []ExerciceFlag{}

		// Treat legacy MCQ flags as ExerciceFlag
		for _, flag := range params.FlagsMCQ {
			params.Flags = append(params.Flags, ExerciceFlag{
				Id:          flag.Id,
				Label:       flag.Label,
				Type:        "mcq",
				ChoicesCost: flag.ChoicesCost,
				Choice:      flag.Choice,
				LockedFile:  flag.LockedFile,
				NeedFlag:    flag.NeedFlag,
			})
		}
		params.FlagsMCQ = []ExerciceFlag{}
	}
	return
}

func GetExerciceFilesParams(i Importer, exercice *fic.Exercice) (map[string]ExerciceFile, error) {
	params, _, err := parseExerciceParams(i, exercice.Path)
	if err != nil {
		return nil, err
	}

	paramsFiles := map[string]ExerciceFile{}
	for _, f := range params.Files {
		paramsFiles[f.Filename] = f
	}

	return paramsFiles, nil
}
