package main

import (
    "flag"
    "fmt"
    "database/sql"
	"log"
	"os"
    "os/exec"
    "io/ioutil"
	"github.com/fatih/color"
	_ "github.com/mattn/go-sqlite3"
    "github.com/rodaine/table"
    "strconv"
    "time"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

const help = `

(·)_(o) HaCkErTaSk 

Basic help
-----------
Create task: htask add -t "My new task".
List task: htask list 
List task filetered: htask list -p blog
View task: htask view <id>
Edit task: htask edit <id>
Modify task: htask mod <id> -P 1
Delete task: htask delete <id>
Mark task as Started: htask start <id>
Mark task as Done: htask done <id>

Check more help in README.md file.
`

func main() {
    // Get HTASK_DB env, if doesnt exists create ~/hackertask.db as database.
    var filedb string
    if os.Getenv("HTASK_DB") == "" {
        var homepath = os.Getenv("HOME")
        filedb = homepath+"/hackertask.db"
    } else {
        filedb = os.Getenv("HTASK_DB")
    }

    checkDB(filedb)
    htaskmain(len(os.Args),filedb)
}

type Task struct {
id *int
name *string
priority *string
project *string 
comments *string
status *string 
createdate *string
startdate *string 
enddate *string 
duedate *string
tags *string 
}


// Main function.
func htaskmain(arguments int,filedb string){
    var t Task
    var existid int

    sqlDB, _ := sql.Open("sqlite3",filedb)
    defer sqlDB.Close()

    if len(os.Args) < 2 {
        fmt.Println(help)
        os.Exit(0)
    }

    switch os.Args[1] {

    case "add":
        addCommand := flag.NewFlagSet("add", flag.ExitOnError)
        t.priority = addCommand.String("P", "5", "Priority")
        t.project = addCommand.String("p", "default", "Project")
        t.name = addCommand.String("t", "", "Task name")
        t.status = addCommand.String("s", "Open", "Status")
        t.duedate = addCommand.String("d", "NoDate", "Due Date")

        addCommand.Parse(os.Args[2:])
        if *t.name == "" {
            fmt.Println("Parameter -t should not be empty")
            os.Exit(0)
        } else {
        addTask(sqlDB,*t.name,*t.status,*t.priority,*t.project,*t.duedate)
        }
        
    case "list":
        listCommand := flag.NewFlagSet("list", flag.ExitOnError)
        t.priority = listCommand.String("P", "%", "Priority")
        t.project = listCommand.String("p", "%", "Project")
        t.status = listCommand.String("s", "%", "Status")
        t.duedate = listCommand.String("d", "%", "Due Date")

        listCommand.Parse(os.Args[2:])
        listTask(sqlDB,*t.priority,*t.project,*t.status,*t.duedate)

    case "mod":
        if len(os.Args) > 2{
            modCommand := flag.NewFlagSet("mod", flag.ExitOnError)
            t.name = modCommand.String("t", "", "Task name")
            t.priority = modCommand.String("P", "", "Priority")
            t.project = modCommand.String("p", "", "Project")
            t.duedate = modCommand.String("d", "", "Due Date")
            modCommand.Parse(os.Args[3:])

            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            if existid != 0 {
                modTask(sqlDB,*t.name,*t.priority,*t.project,*t.duedate,theintid)
            } else {
                fmt.Println("ID dont exist.")
            }
        } else {
            fmt.Println("Usage: htask mod <id>")
        }

    case "view":
        if len(os.Args) > 2{ 
            // Convert param 2 into int.
            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            if existid != 0 {
                viewTask(sqlDB,theintid)
            } else {
                fmt.Println("ID dont exist.")
            }
        } else {
            fmt.Println("Usage: htask view <id>")
        }

    case "delete":
        if len(os.Args) > 2{ 
            // Convert param 2 into int.
            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            if existid != 0 {
                deleteTask(sqlDB,theintid)
            } else {
                fmt.Println("ID dont exist.")
            }
        } else {
            fmt.Println("Usage: htask delete <id>")
        }

    case "done":
        if len(os.Args) > 2{ 
            // Convert param 2 into int.
            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            existDONE := checkStatus(sqlDB,theintid)
            if existid != 0 {
                if existDONE != "Done" {
                    doneTask(sqlDB,theintid)
                } else {
                    fmt.Println("This task was marked as Done in the past.")
                }
            } else {
                fmt.Println("ID dont exist")
            }
        } else {
            fmt.Println("Usage: htask done <id>")
        }

    case "start":
        if len(os.Args) > 2{ 
            // Convert param 2 into int.
            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            existDONE := checkStatus(sqlDB,theintid)
            if existid != 0 {
                if existDONE != "Working" && existDONE != "Done" {
                startTask(sqlDB,theintid)
                } else {
                fmt.Println("This task was started/finished in the past.")
                }
           } else {
                fmt.Println("ID dont exist")
            }
        } else {
            fmt.Println("Usage: htask start <id>")
        }

    case "edit":
        if len(os.Args) > 2{ 
            // Convert param 2 into int.
            theintid, err := strconv.Atoi(os.Args[2])
            if err != nil {
                fmt.Println("Parameter should be the ID.")
                os.Exit(0)
            }
            existid = checkID(sqlDB,theintid)
            if existid != 0 {
                editTask(sqlDB,theintid)
            } else {
                fmt.Println("ID dont exist.")
            }
        } else {
            fmt.Println("Usage: htask edit <id>")
        }

    default:
        fmt.Println(help)
        os.Exit(0)
    }
}

// Func to add Task.
func addTask(db *sql.DB, name string, status string, priority string, project string, duedate string) {
    date := time.Now().Local().String()
	insertSQL := `INSERT INTO htask(name, status, priority, project,createdate,duedate) VALUES (?, ?, ?, ?, ?, ?)`
	statement, err := db.Prepare(insertSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(name, status, priority, project,date,duedate)
	if err != nil {
		log.Fatalln(err.Error())
	}
	color.Green("[+] Task added\n")
}

// Func to mod ify Task.
func modTask(db *sql.DB, name string, priority string, project string, duedate string, id int) {
// Mod name
if name != "" {
    modSQL := `UPDATE htask set name = ? where id = ?`
    statement, err := db.Prepare(modSQL) 
    if err != nil {
        log.Fatalln(err.Error())
    }
    _, err = statement.Exec(name,id)
    if err != nil {
        log.Fatalln(err.Error())
    }
}

// Mod priority
if priority != "" {
	modSQL := `UPDATE htask set priority = ? where id = ?`
	statement, err := db.Prepare(modSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(priority,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
}
// Mod project
if project != "" {
	modSQL := `UPDATE htask set project = ? where id = ?`
	statement, err := db.Prepare(modSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(project,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
}
// Mod duedate
if duedate != "" {
	modSQL := `UPDATE htask set duedate = ? where id = ?`
	statement, err := db.Prepare(modSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(duedate,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
}
//If all ok prints:
color.Green("[+] Task modified\n")
}

// Func to show all Tasks.
func listTask(db *sql.DB, priority string, project string, status string, duedate string) {
    headerFmt := color.New(color.FgMagenta, color.Underline).SprintfFunc()
    columnFmt := color.New(color.FgGreen).SprintfFunc()
    tbl := table.New("ID", "Priority", "Project", "Task","Status","DueDate")
    tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
	row, err := db.Query("SELECT * FROM htask where id like '%' and priority like ? and project like ? and status like ? and duedate like ?",priority,project,status,duedate)
	if err != nil {
		log.Fatal(err)
	}
	defer row.Close()
    fmt.Printf("\n")
	for row.Next() {
		var id int
		var name string
		var priority string
        var project string
        var status string
        var duedate string
        var comments string
        var createdate string
        var startdate string
        var enddate string 
        var tags string
		row.Scan(&id, &name, &priority, &project, &status, &duedate, &comments, &createdate, &startdate, &enddate, &tags)
        tbl.AddRow(id,priority,project,name,status,duedate)
	}

tbl.Print()
fmt.Printf("\n")
}

// Func to view comments.
func viewTask(db *sql.DB, id int) {
	row, err := db.Query("SELECT comments FROM htask where id = ?",id)
	if err != nil {
		log.Fatal(err)
	}
	defer row.Close()
    fmt.Printf("\n")
	for row.Next() {
		var comments string
		row.Scan(&comments)
        fmt.Println(comments)
	}
fmt.Printf("\n")
}

// Func to delete task.
func deleteTask(db *sql.DB, id int) {
	deleteTaskSQL := `DELETE FROM htask WHERE id = ?`
	statement, err := db.Prepare(deleteTaskSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(id)
	if err != nil {
		log.Fatalln(err.Error())
	}
	color.Green("[+] Task %v deleted successfully.\n",id)
}

// Func to done task.
func doneTask(db *sql.DB, id int) {
    date := time.Now().Local().String()
    doneTaskSQL := `UPDATE htask set status = "Done",enddate = ? WHERE id = ?`
	statement, err := db.Prepare(doneTaskSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(date,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
	color.Green("[+] Task %v mark as Done.\n",id)
}

// Func to start task.
func startTask(db *sql.DB, id int) {
    date := time.Now().Local().String()
    startTaskSQL := `UPDATE htask set status = "Working",startdate = ? WHERE id = ?`
	statement, err := db.Prepare(startTaskSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(date,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
	color.Green("[+] Task %v mark as Working.\n",id)
}

// Func to edit task.
func editTask(db *sql.DB, id int) {
    // Obtain current comments.
       var currentcomments string
       row, err := db.Query("SELECT comments from htask where id = ?",id)
        if err != nil {
            log.Fatal(err)
        }
        defer row.Close()
        for row.Next() {
            var comments string
            row.Scan( &comments)
            currentcomments = comments 
}
    // Create temp file with current comments
        var vi string
        if os.Getenv("EDITOR") == "" { 
            vi = "nvim"
        } else {
            vi = os.Getenv("EDITOR")
        }
        tmpDir := os.TempDir()
        tmpFile, tmpFileErr := ioutil.TempFile(tmpDir, "tempFilePrefix")
        if tmpFileErr != nil {
                fmt.Printf("Error %s while creating tempFile", tmpFileErr)
        }
        path, err := exec.LookPath(vi)
        if err != nil {
                fmt.Printf("Error %s while looking up for %s!!", path, vi)
        }
        d1 := []byte(currentcomments)
        err = os.WriteFile(tmpFile.Name(), d1, 0644)
        check(err)
        cmd := exec.Command(path, tmpFile.Name())
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        err = cmd.Start()
        if err != nil {
                fmt.Printf("Start failed: %s", err)
        }
        err = cmd.Wait()
    
    dat, err := os.ReadFile(tmpFile.Name())
    check(err)
    color.Green("[+] Note saved successfully.\n")
    currentdata := string(dat)
    // Update current comments
    updateCommentsSQL := `UPDATE htask set comments = ? where id = ?`
	statement, err := db.Prepare(updateCommentsSQL) 
	if err != nil {
		log.Fatalln(err.Error())
	}
	_, err = statement.Exec(currentdata,id)
	if err != nil {
		log.Fatalln(err.Error())
	}
    // Delete temp file
    os.Remove(tmpFile.Name())
}

// Func to check if ID exists
func checkID(db *sql.DB, id int ) int{
    var total int
    row, err := db.Query("SELECT count(*) as total from htask where id = ?",id)
    if err != nil {
        log.Fatal(err)
    }
    defer row.Close()
    for row.Next() {
        var count int
        row.Scan( &count)
        total = count   
    }
    return total
}

// Func to check if task was marked as Done
func checkStatus(db *sql.DB, id int ) string{
    var resultado string
    row, err := db.Query("SELECT status from htask where id = ?",id)
    if err != nil {
        log.Fatal(err)
    }
    defer row.Close()
    for row.Next() {
        var status string 
        row.Scan( &status)
        resultado = status   
    }
    return resultado
}

// Func to check if DB file exists, if not, it creates the file.
func checkDB(filedb string) {
    _, err := os.Stat(filedb)
    //If file htask.db doesnt exist.
    if err != nil {
        color.Green("[+]Creating Database\n")
        file, err := os.Create(filedb)
        if err != nil {
            log.Fatal(err.Error())
        }
        file.Close()
        sqlDB, _ := sql.Open("sqlite3",filedb)
        defer sqlDB.Close()
        createTable(sqlDB)
    }
    }

// Func to create Database file/schema.
func createTable(db *sql.DB) {
	createHtaskTableSQL := `CREATE TABLE htask (
		"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
		"name" TEXT,
		"priority" TEXT,
        "project" TEXT,
        "status" TEXT,
        "duedate" TEXT,
        "comments" TEXT,
        "createdate" TEXT,
        "startdate" TEXT,
        "enddate" TEXT,
		"tags" TEXT
	  );`

	color.Green("[+]Creating htask table...")
	statement, err := db.Prepare(createHtaskTableSQL) 
	if err != nil {
		log.Fatal(err.Error())
	}
	statement.Exec() 
	color.Green("[+]HackerTask DB created")
}
