package fic

import (
	"fmt"
	"path"
	"strings"
)

// HintCoefficient is the current coefficient applied on its cost lost per showing
var HintCoefficient = 1.0

// EHint represents a challenge hint.
type EHint struct {
	Id int64 `json:"id"`
	// IdExercice is the identifier of the underlying challenge
	IdExercice int64 `json:"idExercice"`
	// Title is the hint name displayed to players
	Title string `json:"title"`
	// Content is the actual content of small text hints (mutually exclusive with File field)
	// When File is filled, Content contains the hexadecimal file's hash.
	Content string `json:"content"`
	// File is path, relative to FilesDir where the file hint is stored (mutually exclusive with Content field)
	File string `json:"file"`
	// Cost is the amount of points the player will loose if it unlocks the hint
	Cost int64 `json:"cost"`
}

// treatHintContent reads Content to detect if this is a hint file in order to convert to such hint.
func treatHintContent(h *EHint) {
	if strings.HasPrefix(h.Content, "$FILES") {
		fpath := strings.TrimPrefix(h.Content, "$FILES")
		h.Content = fpath[:128]
		fpath = fpath[128:]
		h.File = path.Join(FilesDir, fpath)
	}
}

// GetHint retrieves the hint with the given id.
func GetHint(id int64) (*EHint, error) {
	h := &EHint{}
	if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ?", id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil {
		return nil, err
	}
	treatHintContent(h)

	return h, nil
}

// GetHint retrieves the hint with the given id.
func (e *Exercice) GetHint(id int64) (*EHint, error) {
	h := &EHint{}
	if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil {
		return nil, err
	}
	treatHintContent(h)

	return h, nil
}

// GetHintByTitle retrieves the hint with the given id.
func (e *Exercice) GetHintByTitle(id int64) (*EHint, error) {
	h := &EHint{}
	if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE title = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil {
		return nil, err
	}
	treatHintContent(h)

	return h, nil
}

// GetHints returns a list of hints comming with the challenge.
func (e *Exercice) GetHints() ([]*EHint, error) {
	if rows, err := DBQuery("SELECT id_hint, title, content, cost FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil {
		return nil, err
	} else {
		defer rows.Close()

		var hints []*EHint
		for rows.Next() {
			h := &EHint{}
			h.IdExercice = e.Id
			if err := rows.Scan(&h.Id, &h.Title, &h.Content, &h.Cost); err != nil {
				return nil, err
			}
			treatHintContent(h)
			hints = append(hints, h)
		}
		if err := rows.Err(); err != nil {
			return nil, err
		}

		return hints, nil
	}
}

// AddHint creates and fills a new struct EHint and registers it into the database.
func (e *Exercice) AddHint(title string, content string, cost int64) (*EHint, error) {
	if res, err := DBExec("INSERT INTO exercice_hints (id_exercice, title, content, cost) VALUES (?, ?, ?, ?)", e.Id, title, content, cost); err != nil {
		return nil, err
	} else if hid, err := res.LastInsertId(); err != nil {
		return nil, err
	} else {
		return &EHint{hid, e.Id, title, content, "", cost}, nil
	}
}

// Update applies modifications back to the database.
func (h *EHint) Update() (int64, error) {
	if res, err := DBExec("UPDATE exercice_hints SET id_exercice = ?, title = ?, content = ?, cost = ? WHERE id_hint = ?", h.IdExercice, h.Title, h.Content, h.Cost, h.Id); err != nil {
		return 0, err
	} else if nb, err := res.RowsAffected(); err != nil {
		return 0, err
	} else {
		return nb, err
	}
}

// Delete the hint from the database.
func (h *EHint) Delete() (int64, error) {
	if res, err := DBExec("DELETE FROM exercice_hints WHERE id_hint = ?", h.Id); err != nil {
		return 0, err
	} else if nb, err := res.RowsAffected(); err != nil {
		return 0, err
	} else {
		return nb, err
	}
}

// WipeHints deletes (only in the database, not on disk) hints coming with the challenge.
func (e *Exercice) WipeHints() (int64, error) {
	if _, err := DBExec("DELETE FROM exercice_hints_okey_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil {
		return 0, err
	} else if _, err := DBExec("DELETE FROM exercice_hints_omcq_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil {
		return 0, err
	} else if res, err := DBExec("DELETE FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil {
		return 0, err
	} else if nb, err := res.RowsAffected(); err != nil {
		return 0, err
	} else {
		return nb, err
	}
}

// AddDepend insert a new dependency to a given flag.
func (h *EHint) AddDepend(f Flag) (err error) {
	if d, ok := f.(*FlagKey); ok {
		_, err = DBExec("INSERT INTO exercice_hints_okey_deps (id_hint, id_flag_dep) VALUES (?, ?)", h.Id, d.Id)
	} else if d, ok := f.(*MCQ); ok {
		_, err = DBExec("INSERT INTO exercice_hints_omcq_deps (id_hint, id_mcq_dep) VALUES (?, ?)", h.Id, d.Id)
	} else {
		err = fmt.Errorf("dependancy type for key (%T) not implemented for this flag", f)
	}
	return
}

// GetDepends retrieve the flag's dependency list.
func (h *EHint) GetDepends() ([]Flag, error) {
	var deps []Flag

	if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_hints_okey_deps WHERE id_hint = ?", h.Id); err != nil {
		return nil, err
	} else {
		defer rows.Close()

		for rows.Next() {
			var d int
			if err := rows.Scan(&d); err != nil {
				return nil, err
			}
			deps = append(deps, &FlagKey{Id: d, IdExercice: h.IdExercice})
		}
		if err := rows.Err(); err != nil {
			return nil, err
		}
	}

	if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_hints_omcq_deps WHERE id_hint = ?", h.Id); err != nil {
		return nil, err
	} else {
		defer rows.Close()

		for rows.Next() {
			var d int
			if err := rows.Scan(&d); err != nil {
				return nil, err
			}
			deps = append(deps, &MCQ{Id: d, IdExercice: h.IdExercice})
		}
		if err := rows.Err(); err != nil {
			return nil, err
		}
	}

	return deps, nil
}

// GetExercice returns the parent Exercice where this hint can be found.
func (h *EHint) GetExercice() (*Exercice, error) {
	var eid int64
	if err := DBQueryRow("SELECT id_exercice FROM exercice_hints WHERE id_hint = ?", h.Id).Scan(&eid); err != nil {
		return nil, err
	}

	return GetExercice(eid)
}
