package main

import (
    "bufio"
    "os"
    "io/ioutil"
    "fmt"
    "strings"
    "strconv"
    "time"
    "runtime"
    "github.com/go-vgo/robotgo"
)

const (
    MOUSELEFTCLICK = "mouseLeftClick"
    MOUSERIGHTCLICK = "mouseRightClick"
    MOUSEMOVE = "mouseMove"
    SLEEP = "sleep"
    KEYBOARD = "keyboard"
    TAPESCAPE = "tapEscape"
    TAPTAB = "tapTab"
    TAPUP = "tabUp"
    TAPDOWN = "tapDown"
    TAPLEFT = "tapLeft"
    TAPRIGHT = "tabRight"
)

var (
    KEYQ uint16
    KEYW uint16
    KEYE uint16
    KEYA uint16
    KEYS uint16
    KEYD uint16
    KEYR uint16
    KEYZ uint16
    KEYCTRL uint16
    KEYESC uint16
    KEYTAB uint16
    KEYUP uint16
    KEYDOWN uint16
    KEYLEFT uint16
    KEYRIGHT uint16
)

func mainMenu(keyCommands *[]string, runCount *int) string {
    exitChan := make(chan bool, 1)
    go listenRun(exitChan, keyCommands, runCount)
    fmt.Println(`Main Menu

    l: Listening mode
    f: Read from file ("keys.txt")
    s: Save to file ("keys.txt")
    p: Print stored command
    c: Change run count
    r: Run stored command (Hotkey: ctrl-r)
    `)
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter Command: ")
    command, _ := reader.ReadString('\n')
    command = strings.ReplaceAll(command, "\n", "")
    command = strings.ReplaceAll(command, "\r", "")
    exitChan <- true
    return command
}

func listenRun(exitChan chan bool, keyCommands *[]string, runCount *int) {
    time.Sleep(500 * time.Millisecond)
    evtChan := robotgo.Start()
    defer robotgo.End()

    // Rawcode
    // 65507, 162 - ctrl
    // 114, 82 - r

    // Kind
    // 3 - keydown
    // 5 - keyup

    ctrlKey := false
    rKey := false

    for evt := range evtChan {
        // return if exit signal is given
        select {
            case <- exitChan:
                return
            default:
                if (evt.Kind == 3 || evt.Kind == 4) && (evt.Rawcode == KEYCTRL) {
                    ctrlKey = true
                } else if evt.Kind == 5 && (evt.Rawcode == KEYCTRL) {
                    ctrlKey = false
                } else if evt.Kind == 3 && (evt.Rawcode == KEYR) {
                    rKey = true
                } else if evt.Kind == 5 && (evt.Rawcode == KEYR) {
                    rKey = false
                }

                if ctrlKey && rKey {
                    ctrlKey = false
                    rKey = false
                    runKeyCommands(*keyCommands, *runCount)
                    time.Sleep(500 * time.Millisecond)
                }
        }
    }
}

func listenMode(keyCommands *[]string, runCount *int) {
    *keyCommands = nil
    time.Sleep(500 * time.Millisecond)
    fmt.Println("Entering listening mode...")
    evtChan := robotgo.Start()
    defer robotgo.End()

    // Rawcode
    // 113, 81 - q
    // 119, 87 - w
    // 101, 69 - e
    // 97, 65 - a
    // 115, 83 - s
    // 100, 68 - d
    // 65307, 27 - esc

    // Kind
    // 3 - keydown
    // 5 - keyup

    leftMouseTrigger := 0
    rightMouseTrigger := 0
    moveMouseTrigger := 0
    sleepTrigger := 0
    typeTrigger := 0
    escapeTrigger := 0
    escapeTypeTrigger := 0
    tabTrigger := 0
    upTrigger := 0
    downTrigger := 0
    leftTrigger := 0
    rightTrigger := 0

    lastTime := time.Now()
    others := false
    var newCommand string
    for evt := range evtChan {
        // return if exit signal is given
        if evt.Kind == 4 && (evt.Rawcode == KEYQ) {
            leftMouseTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYE) {
            rightMouseTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYW) {
            moveMouseTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYS) {
            sleepTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYD) {
            typeTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYESC) {
            escapeTrigger += 1
            escapeTypeTrigger += 1
        } else if evt.Kind == 5 && (evt.Rawcode == KEYESC) {
            escapeTrigger = 0
            escapeTypeTrigger = 0
        } else if evt.Kind == 4 && (evt.Rawcode == KEYCTRL) {
            escapeTrigger += 1
        } else if evt.Kind == 5 && (evt.Rawcode == KEYCTRL) {
            escapeTrigger = 0
        } else if evt.Kind == 4 && (evt.Rawcode == KEYTAB) {
            tabTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYUP) {
            upTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYDOWN) {
            downTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYLEFT) {
            leftTrigger += 1
        } else if evt.Kind == 4 && (evt.Rawcode == KEYRIGHT) {
            rightTrigger += 1
        }

        if escapeTrigger == 2 {
            return
        } else if leftMouseTrigger == 1 {
            posX, posY := robotgo.GetMousePos()
            newCommand = fmt.Sprintf("%s %d,%d", MOUSELEFTCLICK, posX, posY)
            leftMouseTrigger = 0
        } else if rightMouseTrigger == 1 {
            posX, posY := robotgo.GetMousePos()
            newCommand = fmt.Sprintf("%s %d,%d", MOUSERIGHTCLICK, posX, posY)
            rightMouseTrigger = 0
        } else if moveMouseTrigger == 1 {
            posX, posY := robotgo.GetMousePos()
            newCommand = fmt.Sprintf("%s %d,%d", MOUSEMOVE, posX, posY)
            moveMouseTrigger = 0
        } else if sleepTrigger == 1 {
            newCommand = ""
            sleepTrigger = 0
        } else if typeTrigger == 1 {
            newCommand = fmt.Sprintf("%s \"\"", KEYBOARD)
            typeTrigger = 0
        } else if escapeTypeTrigger == 1 {
            newCommand = TAPESCAPE
            escapeTypeTrigger = 0
        } else if tabTrigger == 1 {
            newCommand =  TAPTAB
            tabTrigger = 0
        } else if upTrigger == 1 {
            newCommand =  TAPUP
            upTrigger = 0
        } else if downTrigger == 1 {
            newCommand = TAPDOWN
            downTrigger = 0
        } else if leftTrigger == 1 {
            newCommand = TAPLEFT
            leftTrigger = 0
        } else if rightTrigger == 1 {
            newCommand = TAPRIGHT
            rightTrigger = 0
        } else {
            others = true
        }

        if !others {
            sleepComamnd := fmt.Sprintf("%s %d", SLEEP, int(time.Since(lastTime).Seconds() * 1000))
            *keyCommands = append(*keyCommands, sleepComamnd)
            if newCommand != "" {
                *keyCommands = append(*keyCommands, newCommand)
            }
            lastTime = time.Now()
        } else  {
            others = false
        }
    }
}

func main() {
    initKeys()
    var keyCommands []string
    var runCount int
    for true {
        x := mainMenu(&keyCommands, &runCount)
        fmt.Println(x)
        if x == "f" {
            keyCommands = readFile("keys.txt")
        } else if x == "c" {
            runCount = changeRunCount()
        } else if x == "p" {
            printKeyCommands(keyCommands)
        } else if x == "r" {
            runKeyCommands(keyCommands, runCount)
        } else if x == "l" {
            listenMode(&keyCommands, &runCount)
        } else if x == "s" {
            writeFile("keys.txt", keyCommands)
        }
    }
}

func initKeys() {
    // 113, 81 - q
    // 119, 87 - w
    // 101, 69 - e
    // 97, 65 - a
    // 115, 83 - s
    // 100, 68 - d
    // 65307, 27 - esc
    // 114, 82 - r
    // 122, 90 - z
    // 65507, 162 - ctrl
    // 65289,9 - tab
    // 65361,37 - left
    // 65362,38 - up
    // 65363,39 - right
    // 65364,40 - down
    if (runtime.GOOS == "windows") {
        KEYQ = 81
        KEYW = 87
        KEYE = 69
        KEYA = 65
        KEYS = 83
        KEYD = 68
        KEYESC = 27
        KEYR = 82
        KEYCTRL = 162
        KEYTAB = 9
        KEYUP = 38
        KEYDOWN = 40
        KEYLEFT = 37
        KEYRIGHT = 39
        KEYZ = 90
    } else {
        KEYQ = 113
        KEYW = 119
        KEYE = 101
        KEYA = 97
        KEYS = 115
        KEYD = 100
        KEYESC = 65307
        KEYR = 114
        KEYCTRL = 65507
        KEYTAB = 65289
        KEYUP = 65362
        KEYDOWN = 65364
        KEYLEFT = 65361
        KEYRIGHT = 65363
        KEYZ = 122
    }
}

func readFile(filename string) []string {
    filecontent, err := ioutil.ReadFile(filename)
    if err != nil {
        fmt.Println(err)
    }
    keyCommands := strings.Split(string(filecontent), "\n")
    return keyCommands
}

func writeFile(filename string, keyCommand []string) {
    fmt.Println("Writing instructions to file...")
    f, err := os.Create(filename)
    defer f.Close()
    if err != nil {
        fmt.Println(err)
    }
    for _, keyCommand := range keyCommand {
        f.WriteString(keyCommand)
        f.WriteString("\n")
    }
}

func changeRunCount() int {
    var runCount int
    fmt.Print("Enter run count: ")
    reader := bufio.NewReader(os.Stdin)
    countStr, _ := reader.ReadString('\n')
    countStr = strings.ReplaceAll(countStr, "\n", "")
    countStr = strings.ReplaceAll(countStr, "\r", "")
    runCount, err := strconv.Atoi(countStr)
    if err != nil {
        fmt.Println(err)
        fmt.Println("Setting run count as 1...")
        runCount = 1
    }
    return runCount
}

func printKeyCommands(keyCommands []string) {
    fmt.Println("------------------------------")
    for _, keyCommand := range keyCommands {
        fmt.Println(keyCommand)
    }
    fmt.Println("------------------------------")
}

func runKeyCommands(keyCommands []string, runCount int) {
    fmt.Printf("Running commands for %d times\n", runCount)
    for i:=0; i<runCount; i++ {
        for _, keyCommand := range(keyCommands) {
            parseKeyCommand(keyCommand)
        }
    }
}

func parseKeyCommand(keyCommand string) {
    // Mouse left click
    if strings.Index(keyCommand, MOUSELEFTCLICK) == 0 {
        args := strings.Split(keyCommand[len(MOUSELEFTCLICK)+1:len(keyCommand)], ",")
        for n, _ := range(args) {
            args[n] = strings.ReplaceAll(args[n], " ", "")
        }
        argX, err := strconv.Atoi(args[0])
        if err != nil {
            fmt.Println(err)
        }
        argY, err := strconv.Atoi(args[1])
        if err != nil {
            fmt.Println(err)
        }
        robotgo.MoveClick(argX, argY, "left", false)
    // Mouse right click
    } else if strings.Index(keyCommand,MOUSERIGHTCLICK) == 0 {
        args := strings.Split(keyCommand[len(MOUSERIGHTCLICK)+1:len(keyCommand)], ",")
        for n, _ := range(args) {
            args[n] = strings.ReplaceAll(args[n], " ", "")
        }
        argX, err := strconv.Atoi(args[0])
        if err != nil {
            fmt.Println(err)
        }
        argY, err := strconv.Atoi(args[1])
        if err != nil {
            fmt.Println(err)
        }
        robotgo.MoveClick(argX, argY, "right", false)
    // Mouse move
    } else if strings.Index(keyCommand, MOUSEMOVE) == 0 {
        args := strings.Split(keyCommand[len(MOUSEMOVE)+1:len(keyCommand)], ",")
        for n, _ := range(args) {
            args[n] = strings.ReplaceAll(args[n], " ", "")
        }
        argX, err := strconv.Atoi(args[0])
        if err != nil {
            fmt.Println(err)
        }
        argY, err := strconv.Atoi(args[1])
        if err != nil {
            fmt.Println(err)
        }
        robotgo.Move(argX, argY)
    // Sleep
    } else if strings.Index(keyCommand, SLEEP) == 0 {
        args := keyCommand[len(SLEEP)+1:len(keyCommand)]
        sleepTime, err := strconv.Atoi(args)
        if err != nil {
            fmt.Println(err)
        }
        time.Sleep(time.Duration(sleepTime) * time.Millisecond)
    // Keyboard type
    } else if strings.Index(keyCommand, KEYBOARD) == 0 {
        args := keyCommand[len(KEYBOARD)+1:len(keyCommand)]
        strokes, err := strconv.Unquote(args)
        if err != nil {
            fmt.Println(err)
        }
        robotgo.TypeString(strokes)
    } else if strings.Index(keyCommand, TAPESCAPE) == 0 {
        robotgo.KeyTap("escape")
    } else if strings.Index(keyCommand, TAPTAB) == 0 {
        robotgo.KeyTap("tab")
    } else if strings.Index(keyCommand, TAPUP) == 0 {
        robotgo.KeyTap("up")
    } else if strings.Index(keyCommand, TAPDOWN) == 0 {
        robotgo.KeyTap("down")
    } else if strings.Index(keyCommand, TAPLEFT) == 0 {
        robotgo.KeyTap("left")
    } else if strings.Index(keyCommand, TAPRIGHT) == 0 {
        robotgo.KeyTap("right")
    }
}
