#!/usr/bin/ruby

require 'rubygems'
require 'basecamper'


USAGE = {
  "configure" => ["track configure [url username password ssl? [project-name]]"],
  "set" => ["track set [key value]"],
  "project" => ["track project project-name"],
  "projects" => ["track projects"],
  "log" => ["track log start-time end-time message [project-name]", "track log duration message [project-name]"],
  "undo" => ["track undo [todo-id]"],
  "status" => ["track status"],
  "times" => ["track times"],
  "help" => ["track help [subcommand]"],
  "start" => ["track start [project-name]", "track start start-time [project-name]"],
  "stop" => ["track stop [end-time] message"],
  "cancel" => ["track cancel"],
  "pause" => ["track pause"],
}
COMMANDS = USAGE.keys


# commands

def configure
  configuration! if ARGV.empty?
  
  url = ARGV.shift
  user_name = ARGV.shift
  password = ARGV.shift
  use_ssl = (ARGV.shift == "true")
  
  tracker.configure!(url, user_name, password, use_ssl)
  configuration!
end

def project
  usage! if ARGV.empty?
  tracker.set!("current_project", project_name(ARGV.shift))
  puts "Set current project to #{tracker.current_project}."
end

def projects
  puts "Projects:"
  tracker.projects.values.map {|v| v.capitalize_all}.sort.each {|name| puts "  #{name}"}
end

def set
  variables! if ARGV.empty?
  usage! if ARGV[1].blank?
  
  exceptions = {"ssl" => "use_ssl", "username" => "user_name"}
  key, value = [ARGV.shift, ARGV.shift]
  
  tracker.set!(exceptions[key] || key, value)
  puts "Set #{key} to #{value}."
end

def log
  usage! if ARGV[0].blank? or ARGV[1].blank?
  
  if ARGV[0].to_time and ARGV[1].to_time
    start_time = ARGV.shift.to_time
    end_time = ARGV.shift.to_time
    # for example, if the times given are "10:00" and "1:30", assume the meridian changed
    if start_time > end_time
      if start_time - end_time < (12*60*60)
        end_time += (12*60*60)
      elsif start_time - end_time < (24*60*60)
        end_time += (24*60*60)
      end
    end
    
    duration = ":#{end_time.minutes_since(start_time).round_to(tracker.config.rounding.to_i)}"
  else
    duration = ARGV.shift
  end
  
  message = ARGV.shift
  project = project_name(ARGV.shift)
  
  if time = tracker.log_time(duration, message, project)
    puts "Logged time:"
    puts display(time)
  else
    error! "Couldn't log time; make sure the project name is spelled right."
  end
end

def start
  if tracker.started?
    puts "Already tracking time."
    status
    exit
  elsif tracker.paused?
    return pause
  end
  
  start_time = nil
  project = nil
  if ARGV.any?
    start_time = ARGV.shift if ARGV[0].to_time
    project = project_name(ARGV.shift) if ARGV.any?
    tracker.set!("current_project", project) if project
  end
  
  error! "No project specified.  Specify a project name as the last argument, or set a default project in config.yml." unless tracker.current_project
  
  tracker.start!(start_time)
  puts "Started tracking time for #{tracker.current_project} at #{tracker.start_time.strftime("%I:%M%p").downcase}."
end

def cancel
  error! "Tracker not started.  Use the 'log' command to log a complete time." unless tracker.started? or tracker.paused?
  tracker.cancel!
  puts "Canceled time tracking for #{tracker.current_project}."
end

def pause
  if tracker.paused?
    tracker.start!
    puts "Resumed tracking time at #{tracker.start_time.strftime("%I:%M%p").downcase} with #{tracker.minutes_elapsed} minutes elapsed."
  elsif tracker.started?
    tracker.pause!
    puts "Paused tracking time with #{tracker.minutes_elapsed} minutes elapsed."
  else
    error! "Tracker not started."
  end
end

def stop
  usage! if ARGV.empty?
  error! "Tracker not started.  Use the 'log' command to log a complete time." unless tracker.started?
  
  end_time = ARGV.shift if ARGV[0].to_time
  message = ARGV.shift
  usage! if message.blank?
  
  error! "No project specified.  Specify a project name as the last argument, or set a default project in config.yml." unless tracker.current_project
  
  time = tracker.stop!(message, end_time)
  
  if time
    puts "Logged time:"
    puts display(time)
  else
    error! "Couldn't log time; make sure the project name is spelled right."
  end
end

def times
  if tracker.times.empty?
    puts "No times recorded today."
    return
  end
  
  puts "Today's times:"
  
  projects = tracker.times.map {|time| time.project_id}.uniq
  sums = {}
  projects.each do |id|
    sums[id] = tracker.times.select {|time| time.project_id == id}.map {|time| time.hours.to_f}.sum
  end
  total_sum = sums.values.sum
  
  tracker.times.reverse_each {|time| puts display(time)}
  puts
  puts "Totals:"
  projects.each {|id| puts "  #{tracker.project_name(id)}: #{sums[id]} hours"}
  puts
  puts "  All projects: #{total_sum} hours"
end

def undo
  error! "No times recorded." unless tracker.times.any?
  
  time = tracker.delete_time(ARGV.shift)
  puts "Deleted time:"
  puts display(time)
end

def status
  if tracker.configured?
    puts "Tracker configured correctly, Basecamp communication online."
  else
    puts "Tracker not configured correctly, Basecamp communication offline."
  end
  
  puts "Current project: #{tracker.current_project || "[No project set.]"}"
  
  if tracker.started?
    puts "\nTracking started:\n  #{tracker.start_time.strftime("%I:%M%p")} (#{tracker.minutes_elapsed} minutes elapsed)" 
  elsif tracker.paused?
    puts "\nTracking paused:\n  #{tracker.minutes_elapsed} minutes elapsed."
  else
    puts "\nNot currently tracking time."
  end
end

def help
  usage!(ARGV.shift) if ARGV[0].in?(COMMANDS)
  
  puts "usage: track <subcommand> [args]"
  puts "help:  track help <subcommand>"
  puts "\nProject names can be a beginning fragment."
  puts "\nAvailable subcommands:"
  COMMANDS.sort.each {|command| puts "  #{command}"}
end


# helper

def usage!(command = nil)
  puts "Usage:"
  USAGE[command || @command].each {|msg| puts "  #{msg}"}
  puts
  exit
end

def error!(msg = nil)
  puts msg if msg
  puts
  exit
end

def display(time)
  "  #{time.created_at.strftime("%I:%M%p").downcase} ##{time.id} [#{tracker.project_name(time.project_id)}] #{time.hours} - #{time.description}"
end

def tracker
  @tracker ||= Basecamper.new
end

def project_name(fragment)
  return if fragment.blank? or tracker.projects.nil?
  tracker.projects.values.find {|name| name == fragment or name =~ /^#{fragment}/i}
end

def variables!
  puts "Variables:"
  puts "  url\t\tBasecamp URL. (e.g. thoughtbot.clientsection.com)"
  puts "  username\tBasecamp username."
  puts "  password\tBasecamp password."
  puts "  ssl\t\tWhether your Basecamp uses SSL (https)."
  puts "  rounding\tRound time to the nearest ___ minutes. Set to 0 to disable."
  puts
  exit
end

def configuration!
  puts "Configuration:"
  puts "  url\t\t#{tracker.config.url}"
  puts "  username\t#{tracker.config.user_name}"
  puts "  password\t#{tracker.config.password}"
  puts "  ssl\t\t#{tracker.config.use_ssl}"
  puts "  rounding\t#{tracker.config.rounding}"
  puts
  exit
end

def unconfigured!
  puts
  puts "Tracker not configured correctly, cannot communicate with Basecamp."
  puts
  exit
end


@command = ARGV[0].in?(COMMANDS) ? ARGV.shift : 'help'

requires_connection = COMMANDS.reject {|c| c.in? ["help", "set", "configure"]}
unconfigured! if @command.in?(requires_connection) and !tracker.configured?

puts
self.send(@command)
puts