package fic

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"
)

// DisplayAllFlags doesn't respect the predefined constraint existing between flags.
var DisplayAllFlags bool

// DisplayMCQBadCount activates the report of MCQ bad responses counter.
var DisplayMCQBadCount bool

// HideCaseSensitivity never tells the user if the flag is case sensitive or not.
var HideCaseSensitivity bool

// UnlockedStandaloneExercices unlock this number of standalone exercice.
var UnlockedStandaloneExercices int

// UnlockedStandaloneExercicesByThemeStepValidation unlock this number of standalone exercice for each theme step validated.
var UnlockedStandaloneExercicesByThemeStepValidation float64

// UnlockedStandaloneExercicesByStandaloneExerciceValidation unlock this number of standalone exercice for each standalone exercice validated.
var UnlockedStandaloneExercicesByStandaloneExerciceValidation float64

type myTeamFile struct {
	Path       string `json:"path"`
	Name       string `json:"name"`
	Checksum   string `json:"checksum"`
	Compressed bool   `json:"compressed,omitempty"`
	Size       int64  `json:"size"`
}
type myTeamHint struct {
	HintId  int64  `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content,omitempty"`
	File    string `json:"file,omitempty"`
	Cost    int64  `json:"cost"`
}
type myTeamFlag struct {
	Id          int                    `json:"id,omitempty"`
	Label       string                 `json:"label"`
	Type        string                 `json:"type,omitempty"`
	Placeholder string                 `json:"placeholder,omitempty"`
	Help        string                 `json:"help,omitempty"`
	Unit        string                 `json:"unit,omitempty"`
	Separator   string                 `json:"separator,omitempty"`
	NbLines     uint64                 `json:"nb_lines,omitempty"`
	IgnoreOrder bool                   `json:"ignore_order,omitempty"`
	IgnoreCase  bool                   `json:"ignore_case,omitempty"`
	Multiline   bool                   `json:"multiline,omitempty"`
	CaptureRe   *string                `json:"capture_regexp,omitempty"`
	Solved      *time.Time             `json:"found,omitempty"`
	PSolved     *time.Time             `json:"part_solved,omitempty"`
	Soluce      string                 `json:"soluce,omitempty"`
	Justify     bool                   `json:"justify,omitempty"`
	BonusGain   int64                  `json:"bonus_gain,omitempty"`
	Choices     map[string]interface{} `json:"choices,omitempty"`
	ChoicesCost int64                  `json:"choices_cost,omitempty"`
	Variant     string                 `json:"variant,omitempty"`
	Min         *float64               `json:"min,omitempty"`
	Max         *float64               `json:"max,omitempty"`
	Step        *float64               `json:"step,omitempty"`
	order       int8
}
type myTeamMCQJustifiedChoice struct {
	Label         string     `json:"label"`
	Value         bool       `json:"value,omitempty"`
	Justification myTeamFlag `json:"justification,omitempty"`
}
type myTeamExercice struct {
	ThemeId    int64        `json:"theme_id,omitempty"`
	Disabled   bool         `json:"disabled,omitempty"`
	WIP        bool         `json:"wip,omitempty"`
	Statement  string       `json:"statement"`
	Overview   string       `json:"overview,omitempty"`
	Finished   string       `json:"finished,omitempty"`
	Hints      []myTeamHint `json:"hints,omitempty"`
	Gain       int          `json:"gain"`
	Files      []myTeamFile `json:"files,omitempty"`
	Flags      []myTeamFlag `json:"flags,omitempty"`
	NbFlags    int          `json:"nb_flags,omitempty"`
	SolveDist  int64        `json:"solve_dist,omitempty"`
	SolvedTime *time.Time   `json:"solved_time,omitempty"`
	SolvedRank int64        `json:"solved_rank,omitempty"`
	Tries      int64        `json:"tries,omitempty"`
	TotalTries int64        `json:"total_tries,omitempty"`
	VideoURI   string       `json:"video_uri,omitempty"`
	Resolution string       `json:"resolution,omitempty"`
	Issue      string       `json:"issue,omitempty"`
	IssueKind  string       `json:"issuekind,omitempty"`
}
type MyTeam struct {
	Id        int64                     `json:"team_id"`
	Name      string                    `json:"name"`
	Points    int64                     `json:"score"`
	Points100 int64                     `json:"score100"`
	Members   []*Member                 `json:"members,omitempty"`
	Exercices map[string]myTeamExercice `json:"exercices"`
}

type ByOrder []myTeamFlag

func (a ByOrder) Len() int           { return len(a) }
func (a ByOrder) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByOrder) Less(i, j int) bool { return a[i].order < a[j].order }

func MyJSONTeam(t *Team, started bool) (interface{}, error) {
	ret := MyTeam{}

	// Fill information about the team
	if t == nil {
		ret.Id = 0
	} else {
		ret.Name = t.Name
		ret.Id = t.Id
		points, _ := t.GetPoints()
		ret.Points = int64(float64(points) * GlobalScoreCoefficient)
		ret.Points100 = int64(float64(points) * GlobalScoreCoefficient * 100)
		if members, err := t.GetMembers(); err == nil {
			ret.Members = members
		}
	}

	// Retrieve themes
	themes, err := GetThemes()
	if err != nil {
		return ret, err
	}

	mapthemes := map[int64]*Theme{}
	for _, theme := range themes {
		mapthemes[theme.Id] = theme
	}

	// Fill exercices, only if the challenge is started
	ret.Exercices = map[string]myTeamExercice{}
	if exos, err := GetDiscountedExercices(); err != nil {
		return ret, err
	} else if started {
		for _, e := range exos {
			if t == nil || ((!e.Disabled || (e.IdTheme != nil && !mapthemes[*e.IdTheme].Locked)) && t.HasAccess(e)) {
				exercice := myTeamExercice{}
				exercice.Disabled = e.Disabled
				exercice.WIP = e.WIP
				if e.IdTheme != nil {
					exercice.ThemeId = *e.IdTheme
				}

				exercice.Statement = strings.Replace(e.Statement, "$FILES$", FilesDir, -1)

				if len(e.Issue) > 0 {
					exercice.Issue = e.Issue
					exercice.IssueKind = e.IssueKind
				}

				if t == nil {
					exercice.Overview = strings.Replace(e.Overview, "$FILES$", FilesDir, -1)
					exercice.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1)
					exercice.VideoURI = strings.Replace(e.VideoURI, "$FILES$", FilesDir, -1)
					exercice.Resolution = strings.Replace(e.Resolution, "$FILES$", FilesDir, -1)
					exercice.TotalTries = e.TriedCount()
					exercice.Gain = int(float64(e.Gain) * e.Coefficient * GlobalScoreCoefficient)
				} else {
					stime := t.HasSolved(e)
					exercice.SolvedTime = stime

					if stime != nil {
						exercice.SolvedRank, _ = t.GetSolvedRank(e)
						exercice.Finished = e.Finished
						exercice.Tries, _ = t.CountTries(e)
					} else {
						var ttime *time.Time
						exercice.Tries, ttime = t.CountTries(e)
						exercice.SolvedTime = ttime
						if DisplayMCQBadCount && exercice.Tries > 0 {
							exercice.SolveDist = t.LastTryDist(e)
						}
					}

					if exercice.SolvedTime != nil && exercice.SolvedTime.Equal(time.Unix(0, 0)) {
						exercice.SolvedTime = nil
					}

					if gain, err := e.EstimateGain(t, stime != nil); err == nil {
						exercice.Gain = int(gain * GlobalScoreCoefficient)
					} else {
						log.Printf("ERROR during gain estimation (tid=%d ; eid=%d): %s", t.Id, e.Id, err.Error())
						exercice.Gain = int(float64(e.Gain) * GlobalScoreCoefficient)
					}
				}

				// Expose exercice files

				exercice.Files = []myTeamFile{}

				if files, err := e.GetFiles(); err != nil {
					return nil, err
				} else {
					for _, f := range files {
						if t == nil || t.CanDownload(f) {
							cksum := f.Checksum
							compressed := false
							if len(f.ChecksumShown) > 0 {
								cksum = f.ChecksumShown
								compressed = true
							}
							exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size})
						}
					}
				}

				// Expose exercice hints

				exercice.Hints = []myTeamHint{}

				if hints, err := e.GetHints(); err != nil {
					return nil, err
				} else {
					for _, h := range hints {
						if t == nil || HintCoefficient < 0 || t.HasHint(h) {
							exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, h.Content, h.File, int64(float64(h.Cost) * GlobalScoreCoefficient)})
						} else if t.CanSeeHint(h) {
							exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, "", "", int64(float64(h.Cost) * GlobalScoreCoefficient)})
						}
					}
				}

				// Expose exercice flags

				if !e.Disabled {
					justifiedMCQ := map[int]myTeamFlag{}

					if labels, err := e.GetFlagLabels(); err != nil {
						return nil, err
					} else {
						for _, l := range labels {
							if !DisplayAllFlags && t != nil && !t.CanSeeFlag(l) {
								// Dependancy missing, skip the flag for now
								continue
							}

							exercice.Flags = append(exercice.Flags, myTeamFlag{
								order:   l.Order,
								Label:   l.Label,
								Variant: l.Variant,
							})
						}
					}

					if flags, err := e.GetFlagKeys(); err != nil {
						return nil, err
					} else {
						for _, k := range flags {
							if !strings.HasPrefix(k.Type, "label") {
								exercice.NbFlags += 1
							}

							if !DisplayAllFlags && t != nil && !t.CanSeeFlag(k) {
								// Dependancy missing, skip the flag for now
								continue
							}

							flag := myTeamFlag{
								Id:    k.Id,
								Type:  k.Type,
								order: k.Order,
								Help:  k.Help,
								Unit:  k.Unit,
							}

							if strings.HasPrefix(flag.Type, "number") {
								flag.Min, flag.Max, flag.Step, err = AnalyzeNumberFlag(flag.Type)
								flag.Type = "number"
								if err != nil {
									return nil, err
								}
							}

							// Retrieve solved state or solution for public iface
							if t == nil {
								flag.IgnoreCase = k.IgnoreCase
								flag.CaptureRe = k.CaptureRegexp
								flag.Soluce = hex.EncodeToString(k.Checksum)
							} else {
								if PartialValidation {
									flag.Solved = t.HasPartiallySolved(k)
								}
								if !HideCaseSensitivity {
									flag.IgnoreCase = k.IgnoreCase
								}
							}

							flag.Multiline = k.Multiline
							flag.BonusGain = int64(float64(k.BonusGain) * GlobalScoreCoefficient)

							var fl FlagMCQLabel
							if fl, err = k.GetMCQJustification(); err == nil {
								k.Label = fl.Label
							}

							// Treat array flags
							flag.Label, flag.Separator, flag.IgnoreOrder, flag.NbLines = k.AnalyzeFlagLabel()

							// Fill more information if the flag is displaied
							if flag.Solved == nil {
								flag.Placeholder = k.Placeholder
								if choices, err := k.GetChoices(); err != nil {
									return nil, err
								} else if t == nil || WChoiceCoefficient < 0 || k.ChoicesCost == 0 || t.SeeChoices(k) {
									flag.Choices = map[string]interface{}{}
									for _, c := range choices {
										flag.Choices[c.Value] = c.Label
									}
								} else {
									flag.ChoicesCost = int64(float64(k.ChoicesCost) * GlobalScoreCoefficient)
								}
							}

							// Append to corresponding flags' map
							if fl.IdChoice != 0 {
								justifiedMCQ[fl.IdChoice] = flag
							} else {
								exercice.Flags = append(exercice.Flags, flag)
							}
						}
					}

					if mcqs, err := e.GetMCQ(); err != nil {
						return nil, err
					} else {
						for _, mcq := range mcqs {
							exercice.NbFlags += 1

							if !DisplayAllFlags && t != nil && !t.CanSeeFlag(mcq) {
								// Dependancy missing, skip the flag for now
								continue
							}

							m := myTeamFlag{
								Id:      mcq.Id,
								Type:    "mcq",
								order:   mcq.Order,
								Label:   mcq.Title,
								Choices: map[string]interface{}{},
							}

							soluce := ""
							fullySolved := true
							if t != nil {
								m.PSolved = t.HasPartiallySolved(mcq)
							}

							for _, e := range mcq.Entries {
								if e.Response {
									soluce += "t"
								} else {
									soluce += "f"
								}

								if v, ok := justifiedMCQ[e.Id]; ok {
									m.Justify = true

									if m.PSolved != nil || v.Solved != nil {
										jc := myTeamMCQJustifiedChoice{
											Label:         e.Label,
											Value:         v.Solved != nil,
											Justification: v,
										}

										if v.Solved == nil {
											fullySolved = false
										}

										if PartialValidation && m.PSolved != nil {
											jc.Value = e.Response
										}

										m.Choices[strconv.Itoa(e.Id)] = jc
									} else {
										m.Choices[strconv.Itoa(e.Id)] = e.Label
									}
								} else {
									m.Choices[strconv.Itoa(e.Id)] = e.Label
								}
							}

							if t == nil {
								h, _ := ComputeHashedFlag([]byte(soluce), false, false, nil, false)
								m.Soluce = hex.EncodeToString(h[:])
							}

							if fullySolved {
								m.Solved = m.PSolved
								m.PSolved = nil
							}

							exercice.Flags = append(exercice.Flags, m)
						}
					}

					// Sort flags by order
					sort.Sort(ByOrder(exercice.Flags))
				}

				// Hash table ordered by exercice Id
				ret.Exercices[fmt.Sprintf("%d", e.Id)] = exercice
			}
		}
	}

	return ret, nil
}

func ReadMyJSON(fd *os.File) (my MyTeam, err error) {
	jdec := json.NewDecoder(fd)

	err = jdec.Decode(&my)
	return
}
