#!/usr/bin/ruby
# Filename:	timer
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License/
# Description:	Keep a timer in a file with keyboard control
require 'fileutils'

##################################################
# Usage
##################################################
PROGNAME = File.basename($0)

DEFAULTS = {
	:o => '/tmp/timer.txt',
	:str => '%t\n',
	:pause => 1,
	:hide => 0,
}

def fatal(*msg)
	msg.each { |m| $stderr.puts "[#{PROGNAME}] ERROR: #{m}" }
	exit(-1);
end

def usage(*msg)
	msg.each { |m| $stderr.puts "ERROR: #{m}" }
	$stderr.puts <<-USAGE

Usage:  #{PROGNAME} [-d] <time> <str>
	Keep a timer in a file with keyboard control
  -o <file>  Specify output file  [DEFAULT: #{DEFAULTS[:o]}]
  -d         Set debug mode

  <time>   Seconds (or <hours>h <mins>m <secs>s)
	String specifies the output for the file:
    \\n   Newline
    %t   Timer value

	USAGE
	exit -1;
end

def strTime(str)
	str = str.dup
	h = str.sub!(/(\d+)h/,'') ? $1.to_i : 0
	m = str.sub!(/(\d+)m/,'') ? $1.to_i : 0
	s = str.sub!(/(\d+)s?/,'') ? $1.to_i : 0
	h*60*60+m*60+s
end

def parseArgs
	opt = DEFAULTS.dup
	loop {
		if (arg=ARGV.shift)==nil then break
		elsif arg == '-h' then usage
		elsif arg == '-?' then usage
		elsif arg == '-o' then opt[:o] = ARGV.shift
		elsif arg =~ /^-/ then usage("Unknown arg [#{arg}]")
		elsif opt[:time] then opt[:str] = arg
		else opt[:time] = strTime(arg)
		end
	}

	opt[:hide] = 1 unless opt[:time]
	opt[:time] ||= 0
	#usage("Need to specify timer") unless opt[:time]

	opt
end

##################################################
# Main code
##################################################
def timeStr(opt,key=:time)
	sec = opt[key]
	return nil unless sec
	return "TIME IS UP" if sec==0
	return "#{sec}s" if sec<60
	min = (sec/60).to_i
	sec -= min*60
	return "#{min}m #{sec}s" if min<60
	hours = (min/60).to_i
	min -= hours*60
	return "#{hours}h #{min}m #{sec}s"
end

def evalStr(opt)
	return "  " if opt[:hide]==1
	t1 = timeStr(opt,:savetime)
	tstr = timeStr(opt)
	tstr = t1 ? "[#{t1}]\n    "+tstr : tstr
	opt[:str].gsub('%t',tstr).gsub('\n',"\n")
end

def writeFile(opt)
	new = opt[:o]+'.new'
	File.open(new,'w') { |f| f.write evalStr(opt) }
	FileUtils.mv(new,opt[:o])	# Needs to be done atomically to avoid breaking ffmpeg
end

def getCommand(opt)
	tstr = timeStr(opt)
p opt[:pause]
	tstr += " (paused)" unless opt[:pause]
	STDERR.print "cmd #{tstr} (stop/run/\"str\"/quit/hide/trial/endtrial/+/-/=)> "
	STDIN.gets.chomp
end

def commands(opt)
	while cmd = getCommand(opt)
		return if cmd.match(/^q/i)
		if cmd.match(/^s/)
			puts "Timer paused"
			opt[:pause] = 1
		elsif cmd.match(/^r/)
			puts "Timer started"
			opt[:pause] = 0
		elsif cmd.match(/^h/)
			opt[:hide] = opt[:hide]==1 ? 0 : 1
			puts opt[:hide]==1 ? "Timer hidden ('h' to unhide)" : "Timer unhidden"
		elsif cmd==''
			opt[:pause] = opt[:pause]==1 ? 0 : 1
			puts opt[:pause]==1 ? "Timer paused" : "Timer unpaused"
		elsif cmd.match(/^["'](.*)["']/)
			opt[:str] = $1
			puts "Updated output string"
		elsif cmd.match(/^t/)
			if (opt[:savetime])
				puts "ERROR:  In trial"
				next
			end
			puts "Starting trial"
			opt[:pause] = 1
			opt[:savetime] = opt[:time]
			opt[:time] = 60
		elsif cmd.match(/^e/)
			unless (opt[:savetime])
				puts "ERROR:  Not in trial"
				next
			end
			puts "Ending trial"
			opt[:pause] = 1
			opt[:time] = opt[:savetime]
			opt[:savetime] = nil
			opt[:pause] = 0
		elsif cmd.match(/^\+(.+)/)
			opt[:time] += strTime($1)
		elsif cmd.match(/^\-(.+)/)
			opt[:time] -= strTime($1)
		elsif cmd.match(/^\=(.+)/)
			opt[:time] = strTime($1)
		end
	end
end

def timer(opt)
	while true
		opt[:time] -= 1 unless opt[:pause]==1
		opt[:time] = 0 if opt[:time]<0
		writeFile(opt)
		sleep 1
	end
end

def main
	opt = parseArgs	
	timerThread = Thread.new { timer(opt) }
	commandThread = Thread.new { commands(opt) }
	commandThread.join
end
main
