package main

import (
	"database/sql"
	"flag"
	"github.com/gin-gonic/gin"
	_ "github.com/mattn/go-sqlite3"
	"net/http"
)

// define global db
var db, _ = sql.Open("sqlite3", "./database/data.db")

// types representing json queries

type loginInput struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

type userData struct {
	UID      int    `json:"uid"`
	Name     string `json:"name"`
	Password string `json:"password"`
	Role     string `json:"role"`
	Points   int    `json:"points"`
}

type task struct {
	TID    int    `json:"tid"`
	Name   string `json:"name"`
	Points int    `json:"points"`
}

type taskArray struct {
	Tasks []task `json:"tasks"`
}

type newHistoryData struct {
	UID          int    `json:"uid"`
	TID          int    `json:"tid"`
	Time         string `json:"time"`
	PointsGained int    `json:"pointsGained"`
}

type historyData struct {
	User         string `json:"user"`
	Task         string `json:"task"`
	Time         string `json:"time"`
	PointsGained int    `json:"pointsGained"`
}

type historyArray struct {
	History []historyData `json:"history"`
}

type userPointsData struct {
	Name   string `json:"name"`
	Points int    `json:"points"`
}

type userPointsArray struct {
	UserPoints []userPointsData `json:"userPoints"`
}

// log the user into their account
func login(c *gin.Context) {
	// get the username and password from the request
	var requestedUser loginInput
	if err := c.BindJSON(&requestedUser); err != nil {
		c.IndentedJSON(http.StatusBadRequest, requestedUser)
		return
	}
	// check for user using given credentials
	stmt, err := db.Prepare("SELECT * FROM users WHERE name=?")
	checkErr(err)
	defer stmt.Close()
	var user userData
	err = stmt.QueryRow(requestedUser.Name).Scan(&user.UID, &user.Name, &user.Password, &user.Role, &user.Points)
	if err != nil {
		// search failed user not real
		c.IndentedJSON(http.StatusNotFound, requestedUser)
		panic(err)
		return
	}
	if user.Password != requestedUser.Password {
		// user not real
		c.IndentedJSON(http.StatusNotFound, requestedUser)
		panic(err)
		return
	} else {
		// user is in
		c.IndentedJSON(http.StatusOK, user)
	}

}

func getTasks(c *gin.Context) {
	// return a list of all the tasks
	var tasks []task
	// get array of all tasks
	rows, err := db.Query("SELECT * FROM activities")
	checkErr(err)
	for rows.Next() {
		var tempTask task
		err = rows.Scan(&tempTask.TID, &tempTask.Name, &tempTask.Points)
		if err != nil {
			c.IndentedJSON(http.StatusNotFound, tasks)
			return
		}
		tasks = append(tasks, tempTask)
	}
	rows.Close()
	var jsonTasks taskArray
	jsonTasks.Tasks = tasks
	c.IndentedJSON(http.StatusOK, jsonTasks)
}

func completeTask(c *gin.Context) {
	// get the task data from the request
	var completedTask newHistoryData
	if err := c.BindJSON(&completedTask); err != nil {
		c.IndentedJSON(http.StatusBadRequest, completedTask)
		return
	}
	// insert data to history table
	stmt, err := db.Prepare("INSERT INTO history (uid, taskid, time, pointsGained) VALUES (?, ?, ?, ?)")
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, completedTask)
		return
	}
	_, err = stmt.Exec(completedTask.UID, completedTask.TID, completedTask.Time, completedTask.PointsGained)
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, completedTask)
		return
	}

	// add points to user table
	stmt, err = db.Prepare("UPDATE users SET points = points + ? WHERE uid=?")
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, completedTask)
		return
	}
	_, err = stmt.Exec(completedTask.PointsGained, completedTask.UID)
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, completedTask)
		return
	}

	c.IndentedJSON(http.StatusOK, completedTask)
}

func getHistory(c *gin.Context) {
	// get the log of past points gained
	var history []newHistoryData
	// get array of all history Data
	rows, err := db.Query("SELECT * FROM history")
	checkErr(err)
	for rows.Next() {
		var tempHistoryData newHistoryData
		err = rows.Scan(&tempHistoryData.UID, &tempHistoryData.TID, &tempHistoryData.Time, &tempHistoryData.PointsGained)
		if err != nil {
			c.IndentedJSON(http.StatusNotFound, history)
			return
		}
		history = append(history, tempHistoryData)
	}
	rows.Close()

	var historyRes []historyData
	// make the data human-readable
	for i := 0; i < len(history); i++ {
		var tempHistory historyData
		// get the username
		var tempUser userData
		stmt, err := db.Prepare("SELECT * FROM users WHERE uid=?")
		checkErr(err)
		err = stmt.QueryRow(history[i].UID).Scan(&tempUser.UID, &tempUser.Name, &tempUser.Password, &tempUser.Role, &tempUser.Points)
		checkErr(err)
		tempHistory.User = tempUser.Name
		stmt.Close()

		// get the task name and points
		var tempTask task
		stmt, err = db.Prepare("SELECT * FROM activities WHERE taskId=?")
		checkErr(err)
		err = stmt.QueryRow(history[i].TID).Scan(&tempTask.TID, &tempTask.Name, &tempTask.Points)
		checkErr(err)
		tempHistory.Task = tempTask.Name
		tempHistory.PointsGained = tempTask.Points
		stmt.Close()

		tempHistory.Time = history[i].Time

		historyRes = append(historyRes, tempHistory)
	}

	var jsonHistory historyArray
	jsonHistory.History = historyRes
	c.IndentedJSON(http.StatusOK, jsonHistory)
}

func addTask(c *gin.Context) {
	// get the data of the new task
	var newTask task
	if err := c.BindJSON(&newTask); err != nil {
		c.IndentedJSON(http.StatusBadRequest, newTask)
		return
	}
	// insert new task into the task table
	stmt, err := db.Prepare("INSERT INTO activities (name, points) VALUES (?,?)")
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, newTask)
		return
	}
	_, err = stmt.Exec(newTask.Name, newTask.Points)
	if err != nil {
		c.IndentedJSON(http.StatusNotModified, newTask)
		return
	}
	c.IndentedJSON(http.StatusOK, newTask)
}

func getUserPoints(c *gin.Context) {
	// get the names of all users
	var allUsers []userPointsData

	rows, err := db.Query("SELECT name, points FROM users WHERE role='user'")
	checkErr(err)

	// put them in an array of users
	for rows.Next() {
		var tempUser userPointsData
		err = rows.Scan(&tempUser.Name, &tempUser.Points)
		if err != nil {
			c.IndentedJSON(http.StatusNotFound, allUsers)
			return
		}
		allUsers = append(allUsers, tempUser)
	}
	rows.Close()

	// return to the requester
	var jsonUserPoints userPointsArray
	jsonUserPoints.UserPoints = allUsers
	c.IndentedJSON(http.StatusOK, jsonUserPoints)
}

func main() {
	var port string
	flag.StringVar(&port, "port", "localhost:8080", "define the port you want the program to use to serve")
	flag.Parse()
	//release mode
	//gin.SetMode(gin.ReleaseMode)
	router := gin.Default()

	// api routes
	router.POST("/login", login)
	router.POST("/completeTask", completeTask)
	router.POST("/addTask", addTask)
	router.GET("/getTasks", getTasks)
	router.GET("/getHistory", getHistory)
	router.GET("/getUserPoints", getUserPoints)

	// page routes
	router.LoadHTMLGlob("frontend/*")
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{})
	})
	router.Static("/frontend", "./frontend")

	router.Run(port)
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}
