package api

import (
	"fmt"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"

	"srs.epita.fr/fic-server/admin/sync"
	"srs.epita.fr/fic-server/libfic"

	"github.com/gin-gonic/gin"
)

func declareGlobalExercicesRoutes(router *gin.RouterGroup) {
	router.GET("/resolutions.json", exportResolutionMovies)
	router.GET("/exercices_stats.json", getExercicesStats)
	router.GET("/tags", listTags)
}

func declareExercicesRoutes(router *gin.RouterGroup) {
	router.GET("/exercices", listExercices)
	router.POST("/exercices", createExercice)

	apiExercicesRoutes := router.Group("/exercices/:eid")
	apiExercicesRoutes.Use(ExerciceHandler)
	apiExercicesRoutes.GET("", showExercice)
	apiExercicesRoutes.PUT("", updateExercice)
	apiExercicesRoutes.PATCH("", partUpdateExercice)
	apiExercicesRoutes.DELETE("", deleteExercice)

	apiExercicesRoutes.GET("/stats.json", getExerciceStats)

	apiExercicesRoutes.GET("/history.json", getExerciceHistory)

	apiHistoryRoutes := apiExercicesRoutes.Group("/history.json")
	apiHistoryRoutes.Use(AssigneeCookieHandler)
	apiHistoryRoutes.PUT("", appendExerciceHistory)
	apiHistoryRoutes.PATCH("", updateExerciceHistory)
	apiHistoryRoutes.DELETE("", delExerciceHistory)

	apiExercicesRoutes.GET("/hints", listExerciceHints)
	apiExercicesRoutes.POST("/hints", createExerciceHint)

	apiHintsRoutes := apiExercicesRoutes.Group("/hints/:hid")
	apiHintsRoutes.Use(HintHandler)
	apiHintsRoutes.GET("", showExerciceHint)
	apiHintsRoutes.PUT("", updateExerciceHint)
	apiHintsRoutes.DELETE("", deleteExerciceHint)
	apiHintsRoutes.GET("/dependancies", showExerciceHintDeps)

	apiExercicesRoutes.GET("/flags", listExerciceFlags)
	apiExercicesRoutes.POST("/flags", createExerciceFlag)

	apiFlagsRoutes := apiExercicesRoutes.Group("/flags/:kid")
	apiFlagsRoutes.Use(FlagKeyHandler)
	apiFlagsRoutes.GET("", showExerciceFlag)
	apiFlagsRoutes.PUT("", updateExerciceFlag)
	apiFlagsRoutes.POST("/try", tryExerciceFlag)
	apiFlagsRoutes.DELETE("/", deleteExerciceFlag)
	apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps)
	apiFlagsRoutes.GET("/choices/", listFlagChoices)
	apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid")
	apiFlagsChoicesRoutes.Use(FlagChoiceHandler)
	apiFlagsChoicesRoutes.GET("", showFlagChoice)
	apiFlagsRoutes.POST("/choices/", createFlagChoice)
	apiFlagsChoicesRoutes.PUT("", updateFlagChoice)
	apiFlagsChoicesRoutes.DELETE("", deleteFlagChoice)

	apiQuizRoutes := apiExercicesRoutes.Group("/quiz/:qid")
	apiQuizRoutes.Use(FlagQuizHandler)
	apiExercicesRoutes.GET("/quiz", listExerciceQuiz)
	apiQuizRoutes.GET("", showExerciceQuiz)
	apiQuizRoutes.PUT("", updateExerciceQuiz)
	apiQuizRoutes.DELETE("", deleteExerciceQuiz)
	apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps)

	apiExercicesRoutes.GET("/tags", listExerciceTags)
	apiExercicesRoutes.POST("/tags", addExerciceTag)
	apiExercicesRoutes.PUT("/tags", updateExerciceTags)

	declareFilesRoutes(apiExercicesRoutes)
	declareExerciceClaimsRoutes(apiExercicesRoutes)

	// Remote
	router.GET("/remote/themes/:thid/exercices/:exid", sync.ApiGetRemoteExercice)
	router.GET("/remote/themes/:thid/exercices/:exid/hints", sync.ApiGetRemoteExerciceHints)
	router.GET("/remote/themes/:thid/exercices/:exid/flags", sync.ApiGetRemoteExerciceFlags)
}

type Exercice struct {
	*fic.Exercice
	ForgeLink string `json:"forge_link,omitempty"`
}

func ExerciceHandler(c *gin.Context) {
	eid, err := strconv.ParseInt(string(c.Params.ByName("eid")), 10, 32)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid exercice identifier"})
		return
	}

	var exercice *fic.Exercice
	if theme, exists := c.Get("theme"); exists {
		exercice, err = theme.(*fic.Theme).GetExercice(int(eid))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"})
			return
		}
	} else {
		exercice, err = fic.GetExercice(eid)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"})
			return
		}

		if exercice.IdTheme != nil {
			theme, err = exercice.GetTheme()
			if err != nil {
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find the attached theme."})
				return
			}

			c.Set("theme", theme)
		} else {
			c.Set("theme", &fic.Theme{Path: sync.StandaloneExercicesDirectory})
		}
	}

	c.Set("exercice", exercice)

	c.Next()
}

func HintHandler(c *gin.Context) {
	hid, err := strconv.ParseInt(string(c.Params.ByName("hid")), 10, 32)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid hint identifier"})
		return
	}

	exercice := c.MustGet("exercice").(*fic.Exercice)
	hint, err := exercice.GetHint(hid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Hint not found"})
		return
	}

	c.Set("hint", hint)

	c.Next()
}

func FlagKeyHandler(c *gin.Context) {
	kid, err := strconv.ParseInt(string(c.Params.ByName("kid")), 10, 32)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid flag identifier"})
		return
	}

	var flag *fic.FlagKey
	if exercice, exists := c.Get("exercice"); exists {
		flag, err = exercice.(*fic.Exercice).GetFlagKey(int(kid))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"})
			return
		}
	} else {
		flag, err = fic.GetFlagKey(int(kid))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"})
			return
		}
	}

	c.Set("flag-key", flag)

	c.Next()
}

func FlagChoiceHandler(c *gin.Context) {
	cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 32)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid choice identifier"})
		return
	}

	flagkey := c.MustGet("flag-key").(*fic.FlagKey)
	choice, err := flagkey.GetChoice(int(cid))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Choice not found"})
		return
	}

	c.Set("flag-choice", choice)

	c.Next()
}

func FlagQuizHandler(c *gin.Context) {
	qid, err := strconv.ParseInt(string(c.Params.ByName("qid")), 10, 64)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid quiz identifier"})
		return
	}

	var quiz *fic.MCQ
	if exercice, exists := c.Get("exercice"); exists {
		quiz, err = exercice.(*fic.Exercice).GetMCQById(int(qid))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"})
			return
		}
	} else {
		quiz, err = fic.GetMCQ(int(qid))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"})
			return
		}
	}

	c.Set("flag-quiz", quiz)

	c.Next()
}

func listExercices(c *gin.Context) {
	if theme, exists := c.Get("theme"); exists {
		exercices, err := theme.(*fic.Theme).GetExercices()
		if err != nil {
			log.Println("Unable to listThemedExercices:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."})
			return
		}

		c.JSON(http.StatusOK, exercices)
	} else {
		exercices, err := fic.GetExercices()
		if err != nil {
			log.Println("Unable to listThemedExercices:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."})
			return
		}

		c.JSON(http.StatusOK, exercices)
	}
}

func listTags(c *gin.Context) {
	exercices, err := fic.GetExercices()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	ret := map[string][]*fic.Exercice{}
	for _, exercice := range exercices {
		tags, err := exercice.GetTags()
		if err != nil {
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
			return
		}
		for _, t := range tags {
			if _, ok := ret[t]; !ok {
				ret[t] = []*fic.Exercice{}
			}

			ret[t] = append(ret[t], exercice)
		}
	}

	c.JSON(http.StatusOK, ret)
}

// Generate the csv to export with:
//
//	curl -s http://127.0.0.1:8081/api/resolutions.json | jq -r ".[] | [ .theme,.level,.title, @uri \"https://fic.srs.epita.fr/$(date +%Y)/\\(.videoURI)\" ] | join(\";\")"
func exportResolutionMovies(c *gin.Context) {
	exercices, err := fic.GetExercices()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	export := []map[string]string{}
	for _, exercice := range exercices {
		var tname string
		if exercice.IdTheme != nil {
			theme, err := fic.GetTheme(*exercice.IdTheme)
			if err != nil {
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
				return
			}
			tname = theme.Name
		}
		if len(exercice.VideoURI) > 0 {
			level, _ := exercice.GetLevel()
			export = append(export, map[string]string{
				"videoURI": strings.Replace(exercice.VideoURI, "$FILES$/", "files/", 1),
				"theme":    tname,
				"title":    exercice.Title,
				"level":    fmt.Sprintf("%d", level),
			})
		}
	}

	c.JSON(http.StatusOK, export)
}

func loadFlags(n func() ([]fic.Flag, error)) (interface{}, error) {
	if flags, err := n(); err != nil {
		return nil, err
	} else {
		var ret []fic.Flag

		for _, flag := range flags {
			if f, ok := flag.(*fic.FlagKey); ok {
				if k, err := fic.GetFlagKey(f.Id); err != nil {
					return nil, err
				} else {
					ret = append(ret, k)
				}
			} else if f, ok := flag.(*fic.MCQ); ok {
				if m, err := fic.GetMCQ(f.Id); err != nil {
					return nil, err
				} else {
					ret = append(ret, m)
				}
			} else {
				return nil, fmt.Errorf("Flag type %T not implemented for this flag.", f)
			}
		}

		return ret, nil
	}
}

func listExerciceHints(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	hints, err := exercice.GetHints()
	if err != nil {
		log.Println("Unable to listExerciceHints:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving hints"})
		return
	}

	c.JSON(http.StatusOK, hints)
}

func listExerciceFlags(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	flags, err := exercice.GetFlagKeys()
	if err != nil {
		log.Println("Unable to listExerciceFlags:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice flags"})
		return
	}

	c.JSON(http.StatusOK, flags)
}

func listFlagChoices(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	choices, err := flag.GetChoices()
	if err != nil {
		log.Println("Unable to listFlagChoices:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag choices"})
		return
	}

	c.JSON(http.StatusOK, choices)
}

func listExerciceQuiz(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	quiz, err := exercice.GetMCQ()
	if err != nil {
		log.Println("Unable to listExerciceQuiz:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving quiz list"})
		return
	}

	c.JSON(http.StatusOK, quiz)
}

func showExercice(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	var forgelink string
	if fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter); ok {
		if u, _ := fli.GetExerciceLink(exercice); u != nil {
			forgelink = u.String()
		}
	}

	c.JSON(http.StatusOK, Exercice{exercice, forgelink})
}

func getExerciceHistory(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	history, err := exercice.GetHistory()
	if err != nil {
		log.Println("Unable to getExerciceHistory:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"})
		return
	}

	c.JSON(http.StatusOK, history)
}

type exerciceStats struct {
	IdExercice  int64   `json:"id_exercice,omitempty"`
	TeamTries   int64   `json:"team_tries"`
	TotalTries  int64   `json:"total_tries"`
	SolvedCount int64   `json:"solved_count"`
	FlagSolved  []int64 `json:"flag_solved"`
	MCQSolved   []int64 `json:"mcq_solved"`
	CurrentGain int64   `json:"current_gain"`
}

func getExerciceStats(c *gin.Context) {
	e := c.MustGet("exercice").(*fic.Exercice)

	current_gain := e.Gain
	if fic.DiscountedFactor > 0 {
		decoted_exercice, err := fic.GetDiscountedExercice(e.Id)
		if err == nil {
			current_gain = decoted_exercice.Gain
		} else {
			log.Println("Unable to fetch decotedExercice:", err.Error())
		}
	}

	c.JSON(http.StatusOK, exerciceStats{
		TeamTries:   e.TriedTeamCount(),
		TotalTries:  e.TriedCount(),
		SolvedCount: e.SolvedCount(),
		FlagSolved:  e.FlagSolved(),
		MCQSolved:   e.MCQSolved(),
		CurrentGain: current_gain,
	})
}

func getExercicesStats(c *gin.Context) {
	exercices, err := fic.GetDiscountedExercices()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	ret := []exerciceStats{}
	for _, e := range exercices {
		ret = append(ret, exerciceStats{
			IdExercice:  e.Id,
			TeamTries:   e.TriedTeamCount(),
			TotalTries:  e.TriedCount(),
			SolvedCount: e.SolvedCount(),
			FlagSolved:  e.FlagSolved(),
			MCQSolved:   e.MCQSolved(),
			CurrentGain: e.Gain,
		})
	}

	c.JSON(http.StatusOK, ret)
}

func AssigneeCookieHandler(c *gin.Context) {
	myassignee, err := c.Cookie("myassignee")
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action."})
		return
	}

	aid, err := strconv.ParseInt(myassignee, 10, 32)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: invalid assignee identifier."})
		return
	}

	assignee, err := fic.GetAssignee(aid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: assignee not found."})
		return
	}

	c.Set("assignee", assignee)

	c.Next()
}

type uploadedExerciceHistory struct {
	IdTeam    int64 `json:"team_id"`
	Kind      string
	Time      time.Time
	Secondary *int64
	Coeff     float32
}

func appendExerciceHistory(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)
	myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)

	var uh uploadedExerciceHistory
	err := c.ShouldBindJSON(&uh)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	err = exercice.AppendHistoryItem(uh.IdTeam, uh.Kind, uh.Secondary)
	if err != nil {
		log.Println("Unable to appendExerciceHistory:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history moditication."})
		return
	}
	log.Printf("AUDIT: %s performs an history append: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary)

	c.JSON(http.StatusOK, uh)
}

func updateExerciceHistory(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)
	myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)

	var uh uploadedExerciceHistory
	err := c.ShouldBindJSON(&uh)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	_, err = exercice.UpdateHistoryItem(uh.Coeff, uh.IdTeam, uh.Kind, uh.Time, uh.Secondary)
	if err != nil {
		log.Println("Unable to updateExerciceHistory:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history update."})
		return
	}
	log.Printf("AUDIT: %s performs an history update: %s for team %d, exercice %d and optional %v, with coeff %f", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary, uh.Coeff)

	c.JSON(http.StatusOK, uh)
}

func delExerciceHistory(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)
	myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)

	var uh uploadedExerciceHistory
	err := c.ShouldBindJSON(&uh)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	_, err = exercice.DelHistoryItem(uh.IdTeam, uh.Kind, uh.Time, uh.Secondary)
	if err != nil {
		log.Println("Unable to delExerciceHistory:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history deletion."})
		return
	}
	log.Printf("AUDIT: %s performs an history deletion: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary)

	c.JSON(http.StatusOK, true)
}

func deleteExercice(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	_, err := exercice.DeleteCascade()
	if err != nil {
		log.Println("Unable to deleteExercice:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice deletion"})
		return
	}

	c.JSON(http.StatusOK, true)
}

func updateExercice(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	var ue fic.Exercice
	err := c.ShouldBindJSON(&ue)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	ue.Id = exercice.Id

	if len(ue.Title) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Exercice's title not filled"})
		return
	}

	if _, err := ue.Update(); err != nil {
		log.Println("Unable to updateExercice:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice update"})
		return
	}

	c.JSON(http.StatusOK, ue)
}

type patchExercice struct {
	Language    *string  `json:"lang,omitempty"`
	Title       *string  `json:"title"`
	Disabled    *bool    `json:"disabled"`
	WIP         *bool    `json:"wip"`
	URLId       *string  `json:"urlid"`
	Statement   *string  `json:"statement"`
	Overview    *string  `json:"overview"`
	Headline    *string  `json:"headline"`
	Finished    *string  `json:"finished"`
	Issue       *string  `json:"issue"`
	IssueKind   *string  `json:"issuekind"`
	Gain        *int64   `json:"gain"`
	Coefficient *float64 `json:"coefficient"`
}

func partUpdateExercice(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	var ue patchExercice
	err := c.ShouldBindJSON(&ue)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	for _, field := range reflect.VisibleFields(reflect.TypeOf(ue)) {
		if !reflect.ValueOf(ue).FieldByName(field.Name).IsNil() {
			reflect.ValueOf(exercice).Elem().FieldByName(field.Name).Set(reflect.ValueOf(reflect.ValueOf(ue).FieldByName(field.Name).Elem().Interface()))
		}
	}

	if _, err := exercice.Update(); err != nil {
		log.Println("Unable to partUpdateExercice:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice update."})
		return
	}

	c.JSON(http.StatusOK, exercice)
}

func createExercice(c *gin.Context) {
	theme := c.MustGet("theme").(*fic.Theme)

	// Create a new exercice
	var ue fic.Exercice
	err := c.ShouldBindJSON(&ue)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(ue.Title) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Title not filled"})
		return
	}

	var depend *fic.Exercice = nil
	if ue.Depend != nil {
		if d, err := fic.GetExercice(*ue.Depend); err != nil {
			log.Println("Unable to createExercice:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})
			return
		} else {
			depend = d
		}
	}

	exercice, err := theme.AddExercice(ue.Title, ue.Authors, ue.Image, ue.BackgroundColor, ue.WIP, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Resolution, ue.SeeAlso, ue.Finished)
	if err != nil {
		log.Println("Unable to createExercice:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})
		return
	}

	c.JSON(http.StatusOK, exercice)
}

type uploadedHint struct {
	Title   string
	Path    string
	Content string
	Cost    int64
	URI     string
}

func createExerciceHint(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	var uh uploadedHint
	err := c.ShouldBindJSON(&uh)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uh.Content) != 0 {
		hint, err := exercice.AddHint(uh.Title, uh.Content, uh.Cost)
		if err != nil {
			log.Println("Unable to AddHint in createExerciceHint:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."})
			return
		}

		c.JSON(http.StatusOK, hint)
	} else if len(uh.URI) != 0 {
		hint, err := sync.ImportFile(sync.GlobalImporter, uh.URI,
			func(filePath string, origin string) (interface{}, error) {
				return exercice.AddHint(uh.Title, "$FILES"+strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost)
			})

		if err != nil {
			log.Println("Unable to AddHint (after ImportFile) in createExerciceHint:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."})
			return
		}

		c.JSON(http.StatusOK, hint)
	} else {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's content not filled"})
		return
	}
}

func showExerciceHint(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("hint").(*fic.EHint))
}

func showExerciceHintDeps(c *gin.Context) {
	hint := c.MustGet("hint").(*fic.EHint)

	deps, err := loadFlags(hint.GetDepends)
	if err != nil {
		log.Println("Unable to loaddeps:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."})
		return
	}

	c.JSON(http.StatusOK, deps)
}

func updateExerciceHint(c *gin.Context) {
	hint := c.MustGet("hint").(*fic.EHint)

	var uh fic.EHint
	err := c.ShouldBindJSON(&uh)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	uh.Id = hint.Id

	if len(uh.Title) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's title not filled"})
		return
	}

	if _, err := uh.Update(); err != nil {
		log.Println("Unable to updateExerciceHint:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update hint."})
		return
	}

	c.JSON(http.StatusOK, uh)
}

func deleteExerciceHint(c *gin.Context) {
	hint := c.MustGet("hint").(*fic.EHint)

	_, err := hint.Delete()
	if err != nil {
		log.Println("Unable to deleteExerciceHint:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete hint."})
		return
	}

	c.JSON(http.StatusOK, true)
}

type uploadedFlag struct {
	Type         string
	Label        string
	Placeholder  string
	Help         string
	IgnoreCase   bool
	Multiline    bool
	NoTrim       bool
	CaptureRe    *string `json:"capture_regexp"`
	SortReGroups bool    `json:"sort_re_grps"`
	Flag         string
	Value        []byte
	ChoicesCost  int32 `json:"choices_cost"`
	BonusGain    int32 `json:"bonus_gain"`
}

func createExerciceFlag(c *gin.Context) {
	var uk uploadedFlag
	err := c.ShouldBindJSON(&uk)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uk.Flag) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Flag not filled"})
		return
	}

	var vre *string = nil
	if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
		vre = uk.CaptureRe
	}

	exercice := c.MustGet("exercice").(*fic.Exercice)

	flag, err := exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.NoTrim, uk.Multiline, vre, uk.SortReGroups, []byte(uk.Flag), uk.ChoicesCost, uk.BonusGain)
	if err != nil {
		log.Println("Unable to createExerciceFlag:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to create flag."})
		return
	}

	c.JSON(http.StatusOK, flag)
}

func showExerciceFlag(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("flag-key").(*fic.FlagKey))
}

func showExerciceFlagDeps(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	deps, err := loadFlags(flag.GetDepends)
	if err != nil {
		log.Println("Unable to loaddeps:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."})
		return
	}

	c.JSON(http.StatusOK, deps)
}

func tryExerciceFlag(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	var uk uploadedFlag
	err := c.ShouldBindJSON(&uk)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uk.Flag) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty submission"})
		return
	}

	if flag.Check([]byte(uk.Flag)) == 0 {
		c.AbortWithStatusJSON(http.StatusOK, true)
		return
	}

	c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad submission"})
}

func updateExerciceFlag(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	var uk uploadedFlag
	err := c.ShouldBindJSON(&uk)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uk.Label) == 0 {
		flag.Label = "Flag"
	} else {
		flag.Label = uk.Label
	}

	flag.Placeholder = uk.Placeholder
	flag.Help = uk.Help
	flag.IgnoreCase = uk.IgnoreCase
	flag.Multiline = uk.Multiline
	if len(uk.Flag) > 0 {
		var err error
		flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag))
		if err != nil {
			log.Println("Unable to ComputeChecksum:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to compute flag checksum"})
			return
		}
	} else {
		flag.Checksum = uk.Value
	}
	flag.ChoicesCost = uk.ChoicesCost
	flag.BonusGain = uk.BonusGain

	if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
		flag.CaptureRegexp = uk.CaptureRe
	} else {
		flag.CaptureRegexp = nil
	}

	if _, err := flag.Update(); err != nil {
		log.Println("Unable to updateExerciceFlag:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update flag."})
		return
	}

	c.JSON(http.StatusOK, flag)
}

func deleteExerciceFlag(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	_, err := flag.Delete()
	if err != nil {
		log.Println("Unable to deleteExerciceFlag:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete flag."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func createFlagChoice(c *gin.Context) {
	flag := c.MustGet("flag-key").(*fic.FlagKey)

	var uc fic.FlagChoice
	err := c.ShouldBindJSON(&uc)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uc.Label) == 0 {
		uc.Label = uc.Value
	}

	choice, err := flag.AddChoice(&uc)
	if err != nil {
		log.Println("Unable to createFlagChoice:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to create flag choice."})
		return
	}

	c.JSON(http.StatusOK, choice)
}

func showFlagChoice(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("flag-choice").(*fic.FlagChoice))
}

func updateFlagChoice(c *gin.Context) {
	choice := c.MustGet("flag-choice").(*fic.FlagChoice)

	var uc fic.FlagChoice
	err := c.ShouldBindJSON(&uc)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uc.Label) == 0 {
		choice.Label = uc.Value
	} else {
		choice.Label = uc.Label
	}

	choice.Value = uc.Value

	if _, err := choice.Update(); err != nil {
		log.Println("Unable to updateFlagChoice:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update flag choice."})
		return
	}

	c.JSON(http.StatusOK, choice)
}

func deleteFlagChoice(c *gin.Context) {
	choice := c.MustGet("flag-choice").(*fic.FlagChoice)

	_, err := choice.Delete()
	if err != nil {
		log.Println("Unable to deleteExerciceChoice:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete choice."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func showExerciceQuiz(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("flag-quiz").(*fic.MCQ))
}

func showExerciceQuizDeps(c *gin.Context) {
	quiz := c.MustGet("flag-quiz").(*fic.MCQ)

	deps, err := loadFlags(quiz.GetDepends)
	if err != nil {
		log.Println("Unable to loaddeps:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve quiz dependencies."})
		return
	}

	c.JSON(http.StatusOK, deps)
}

func updateExerciceQuiz(c *gin.Context) {
	quiz := c.MustGet("flag-quiz").(*fic.MCQ)

	var uq fic.MCQ
	err := c.ShouldBindJSON(&uq)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	quiz.Title = uq.Title

	if _, err := quiz.Update(); err != nil {
		log.Println("Unable to updateExerciceQuiz:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update quiz."})
		return
	}

	// Update and remove old entries
	var delete []int
	for i, cur := range quiz.Entries {
		seen := false
		for _, next := range uq.Entries {
			if cur.Id == next.Id {
				seen = true

				if cur.Label != next.Label || cur.Response != next.Response {
					cur.Label = next.Label
					cur.Response = next.Response
					if _, err := cur.Update(); err != nil {
						log.Println("Unable to update MCQ entry:", err.Error())
						c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update some MCQ entry"})
						return
					}
				}

				break
			}
		}

		if seen == false {
			if _, err := cur.Delete(); err != nil {
				log.Println("Unable to delete MCQ entry:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete some MCQ entry"})
				return
			} else {
				delete = append(delete, i)
			}
		}
	}
	for n, i := range delete {
		quiz.Entries = append(quiz.Entries[:i-n-1], quiz.Entries[:i-n+1]...)
	}

	// Add new choices
	for _, choice := range uq.Entries {
		if choice.Id == 0 {
			if ch, err := quiz.AddEntry(choice); err != nil {
				log.Println("Unable to add MCQ entry:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add some MCQ entry"})
				return
			} else {
				quiz.Entries = append(quiz.Entries, ch)
			}
		}
	}

	c.JSON(http.StatusOK, quiz)
}

func deleteExerciceQuiz(c *gin.Context) {
	quiz := c.MustGet("flag-quiz").(*fic.MCQ)

	for _, choice := range quiz.Entries {
		if _, err := choice.Delete(); err != nil {
			log.Println("Unable to deleteExerciceQuiz (entry):", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz entry."})
			return
		}
	}

	_, err := quiz.Delete()
	if err != nil {
		log.Println("Unable to deleteExerciceQuiz:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func listExerciceTags(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	tags, err := exercice.GetTags()
	if err != nil {
		log.Println("Unable to listExerciceTags:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to get tags."})
		return
	}

	c.JSON(http.StatusOK, tags)
}

func addExerciceTag(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	var ut []string
	err := c.ShouldBindJSON(&ut)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	// TODO: a DB transaction should be done here: on error we should rollback
	for _, t := range ut {
		if _, err := exercice.AddTag(t); err != nil {
			log.Println("Unable to addExerciceTag:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add some tag."})
			return
		}
	}

	c.JSON(http.StatusOK, ut)
}

func updateExerciceTags(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	exercice.WipeTags()
	addExerciceTag(c)
}
