package com.jdblabs.timestamper.cli

import com.jdbernard.io.NonBlockingInputStreamReader
import com.jdbernard.util.SmartConfig
import com.jdbernard.util.LightOptionParser
import com.jdblabs.timestamper.core.Timeline
import com.jdblabs.timestamper.core.TimelineMarker
import com.jdblabs.timestamper.core.TimelineProperties
import com.martiansoftware.nailgun.NGContext

import org.fusesource.jansi.AnsiConsole
import static org.fusesource.jansi.Ansi.*

import static org.fusesource.jansi.Ansi.*
import static org.fusesource.jansi.Ansi.Color.*

public class TimeStamperCLI {

    private static String EOL = System.getProperty("line.separator")

    private static TimeStamperCLI nailgunInst

    protected TimelineProperties timelineProperties
    protected Timeline timeline

    public static final String VERSION = "1.2"

    protected static def cli = [
        'h': [longName: 'help'],
        'v': [longName: 'version'],
        'd': [longName: 'working-directory', arguments: 1],
        't': [longName: 'timeline-config', arguments: 1],
        'tty': [longName: 'tty', arguments: 1]]

    public static void main(String[] args) {

        TimeStamperCLI inst = new TimeStamperCLI()
        doMain(inst, args, System.in, System.out, System.err); }

    public static void nailMain(NGContext context) {
        if (nailgunInst == null)
            nailgunInst = new TimeStamperCLI()

        doMain(nailgunInst, context.args, context.in, context.out, context.err) }

    protected static void doMain(TimeStamperCLI inst, String[] args,
    def sin, def out, def err) {

        //out = new PrintStream(AnsiConsole.wrapOutputStream(out))
        def opts = LightOptionParser.parseOptions(cli, args as List)

        File workingDir = new File(opts.d[0] ?: '.')
        String ttyDevice = opts.tty ?: '/dev/tty'

        if (opts.h) println USAGE

        if (opts.v) {
            println "TimeStamperCLI v${VERSION}"
            println "By JDB Labs (https://www.jdb-labs.com)"
            return }

        if (opts.t) {
            File propFile = new File(opts.t)
            if (!propFile.isAbsolute()) propFile = new File(workingDir, opts.t)
            inst.showTimeline(propFile, sin, out, err, ttyDevice) }
        else if (inst.timeline == null) {
            // Look for .timestamperrc user config file
            File cfgFile = new File(
                System.getProperty('user.home'), ".timestamperrc")
            if (!cfgFile.exists() || !cfgFile.isFile())
                err.println "Could not find the user configuration file: " +
                    cfgFile.canonicalPath
            else {
                def cfg = new SmartConfig(cfgFile)
                inst.showTimeline(new File(cfg.lastUsed), sin, out, err, ttyDevice) } }
        else { inst.showTimeline(sin, out, err, ttyDevice) } }

    public TimeStamperCLI() { }

    public void showTimeline(File timelinePropertiesFile,
    def sin, def out, def err, String ttyDevice) {
        if (!timelinePropertiesFile.exists() ||
            !timelinePropertiesFile.isFile()) {

            err.println "No such timeline property file: " +
                timelinePropertiesFile.canonicalPath

            return }

        this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
        this.timeline = timelineProperties.timeline

        showTimeline(sin, out, err, ttyDevice) }

    public void showTimeline(final def sin, def out, def err, String ttyDevice) {

        //out.println ""
        def currentMarker = timeline.getLastMarker(new Date())
        def reader = new NonBlockingInputStreamReader(sin)
        Thread readerThread = new Thread(reader)
        readerThread.start()

        boolean running = true

        def blockingReadLine = {
            String line = null;
            while (line == null && readerThread.isAlive()) {
                line = reader.readLine()

                Thread.sleep(200) }

            return line }

        def readNotes = {
            out.println(ansi().fg(YELLOW).
                a("Notes (end with EOF or a blank line):").reset())

            String notes = ""
            String line = null
            line = blockingReadLine()

            while(line != "" && line != "EOF" && line != null) {
                notes += line + EOL
                line = blockingReadLine() }

            return notes }

        def printPrompt = {
            out.print(formatMarker(currentMarker) + EOL +
                ansi().fg(YELLOW).a("> ").reset())
            out.flush() }

        String line = null

        printPrompt()
        while (running && readerThread.isAlive()) {

           // Handle user input
            line = reader.readLine()
            if (line != null) {
                out.flush();
                switch (line) {
                    case ~/quit|exit|\u0004/:
                        running = false;
                        break

                    case ~/n|new/:

                        // Read mark
                        out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
                        String mark = blockingReadLine()

                        // Read notes
                        String notes = readNotes();

                        // Create marker
                        currentMarker = new TimelineMarker(new Date(), mark, notes)
                        timeline.addMarker(currentMarker)
                        if (timelineProperties.persistOnUpdate)
                            timelineProperties.save()
                        break

                    case ~/h|help/:
                        out.println(ansi().fg(RED).
                            a("Not yet implemented.").reset());
                        break

                    case ~/l|list|history/:
                        out.println(ansi().fg(RED).
                            a("Not yet implemented.").reset());
                        break

                    case ~/s|save/:
                        timelineProperties.save()
                        break

                    case ~/reload/:
                        timeline = timelineProperties.reloadTimeline()
                        currentMarker = timeline.getLastMarker(new Date())
                        break

                    case ~/e|ed|edit/:
                        reader.pause()
                        out.println(ansi().fg(YELLOW).
                            a("Press ENTER to launch an editor for the current marker..."))
                        out.flush()
                        blockingReadLine()
                        currentMarker = edit(currentMarker, ttyDevice)
                        reader.resume()
                        break

                    case ~/d|del|delete/:
                        timeline.removeMarker(currentMarker)
                        currentMarker = timeline.getLastMarker(new Date())
                        break

                    default:
                        String notes = readNotes()
                        currentMarker = new TimelineMarker(new Date(), line, notes)
                        timeline.addMarker(currentMarker)
                        if (timelineProperties.persistOnUpdate)
                            timelineProperties.save()
                        break
                }
                printPrompt()
            } else {
                out.print(ansi().saveCursorPosition().cursorUpLine().eraseLine().toString() +
                    formatMarker(currentMarker) +
                    ansi().cursorDown(1).restorCursorPosition().toString())
                out.flush();

                Thread.sleep(200)
            }
        }

        this.timelineProperties.syncTargets.each { it.shutdown() }

        if (readerThread.isAlive()) {
            readerThread.interrupt();
            readerThread.join(500);
            if (readerThread.isAlive()) readerThread.stop(); }

        out.println ""
    }

    protected TimelineMarker edit(TimelineMarker tm, String ttyDevice) {
        File temp = File.createTempFile('timestamp-mark-', '.txt')
        temp.withPrintWriter { fout ->
            fout.println("""\
# Edit the time, mark, and notes below. Any lines starting with '#' will be
# ignored. When done, save the file and close the editor.""")
            fout.println(Timeline.longFormat.format(tm.timestamp))
            fout.println(tm.mark)
            fout.println("""\
# Everything from the line below to the end of the file will be considered
# notes for this timeline mark.""")
            fout.println(tm.notes) }

        ['sh', '-c', "\$EDITOR ${temp.canonicalPath} <${ttyDevice} >${ttyDevice}"].execute().waitFor()

        Thread.sleep(200)

        String newTimeStr
        String newMark
        String newNotes = ""

        temp.eachLine { line ->
            if (line =~ /^\s*#/) return

            if (!newTimeStr) newTimeStr = line
            else if (!newMark) newMark = line
            else newNotes += line + EOL }

        Date newTime
        try { newTime = Timeline.longFormat.parse(newTimeStr) }
        catch(Exception e) {
            out.println(ansi().fg(RED).a("Invalid timestamp format. The " +
                "previous timestamp value will be retained."))
            newTime = currentMark.timestamp }

        timeline.removeMarker(tm)
        def newMarker = new TimelineMarker(newTime, newMark, newNotes)
        timeline.addMarker(newMarker)
        if (timelineProperties.persistOnUpdate)
            timelineProperties.save()

        return newMarker

    }

    protected static String formatMarker(TimelineMarker tm) {
        return ansi().fgBright(CYAN).
            a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).
            a("(" + getDuration(tm) + ") ").fg(WHITE).a(tm.mark).toString() }

    protected static String getDuration(TimelineMarker tm) {
        Date currentTime = new Date()
        long seconds = currentTime.time - tm.timestamp.time
        seconds /= 1000
        long minutes = seconds / 60
        seconds = seconds % 60
        long hours = minutes / 60
        minutes %= 60
        long days = hours / 24
        hours %= 24

        StringBuilder sb = new StringBuilder()
        if (days > 0) sb.append(days + "day ")
        if (hours > 0) sb.append(hours + "hr ")
        if (minutes > 0) sb.append(minutes + "min ")
        sb.append(seconds + "sec")

        return sb.toString() }

    public static final String USAGE = """\
TimeStamperCLI v${VERSION}
By JDB Labs (https://www.jdb-labs.com)

Usage: 
    ts <OPTIONS>
where OPTIONS is one or more of:

-h, --help                  Print this usage information.

-v, --version               Print version information.

-d, --working-directory     Set the application's working direcotry (defaults to
                            the current directory of the executing process).

-t, --timeline-config       Set the timeline configuration file to use to access
                            the timeline. By default, the value of the
                            `lastUsed` property in the \$HOME/.timestamperrc
                            file is used to find the timeline configuration file
                            to use.

--tty                       Manually set the name of the TTY device to use. This
                            defaults to `/dev/tty`.
"""

}
