#!/usr/bin/ruby
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'gli'
require 'trac4r'
require 'rainbow'
require 'cgi'

include GLI

OPENER_DEFAULT = RUBY_PLATFORM =~ /darwin/ ? 'open' : nil
begin
  # TODO: Figure out a better way to do this
  cols = `stty -a | grep "columns;" | head -1`.split(/;/)
  COLUMNS_DEFAULT = cols[2].gsub(' columns','')
rescue
  COLUMNS_DEFAULT = nil
end

config_file '.trac4r'

desc 'Command to use to open URLs'
default_value OPENER_DEFAULT
flag [:o,:open]

desc 'URL to trac'
flag [:url]

desc 'Your username'
flag [:u,:user]

desc 'Your password'
flag [:p,:password]

desc 'Do not format output'
long_desc 'By default, the output is formatted fo readability in the terminal.  By disabling this, the output is more "machine readable" and parsable by other scripts'
switch [:noformat]

desc 'number of columns for formatting'
default_value COLUMNS_DEFAULT
flag [:cols]

desc 'Open the ticket or wiki page'
arg_name 'the id of the ticket or name of wiki page'
command :open do |c|
  c.action do |global_options,options,args|
    raise "You must supply a ticket ID or WikiName" if args.empty?
    id = args[0]
    if id =~ /^\d+$/
      system "#{global_options[:o]} #{$url}/ticket/#{id}"
    else
      system "#{global_options[:o]} #{$url}/wiki/#{args.map{ |x| x[0..0].upcase + x[1..-1]}.join('')}"
    end
  end
end

desc 'Go to the new ticket web interface'
long_desc 'Navigates your browser to the web interface, initialized with the values you provide, but does not actually create the ticket'
arg_name 'summary for the ticket'
command [:newticket,:nt] do |c|
  c.desc 'component'
  c.flag [:c,:component]

  c.desc 'type'
  c.flag [:t,:type]

  c.desc 'priority'
  c.flag [:p,:priority]

  c.desc 'milestone'
  c.flag [:m,:milestone]

  c.desc 'keywords'
  c.flag [:k,:keywords]

  c.action do |global_options,options,args|
    query_params = {}
    query_params['priority'] = options[:p] if options[:p]
    query_params['component'] = options[:c] if options[:c]
    query_params['type'] = options[:t] if options[:t]
    query_params['milestone'] = options[:m] if options[:m]
    query_params['keywords'] = options[:k] if options[:k]
    query_params['summary'] = args.join(' ')
    query_string = ''
    query_params.each do |key,value|
      query_string += key
      query_string += '='
      query_string += CGI.escape(value)
      query_string += '&'
    end

    url = $url.gsub(/\/xmlrpc/,'') + '/newticket?' + query_string
    system "#{global_options[:o]} \"#{url}\""
  end
end

FORMATTING = {
  'id' => { :color => :green, :size => 6 },
  'status' => { :color => :cyan, :size => 9 },
  'owner' => { :size => 17 },
  'component' => { :size => 10, :color => :magenta },
  'milestone' => { :size => 28, :color => :cyan },
}
desc 'Lists tickets'
arg_name 'ticket ids (blank for all)'
command [:tickets,:ls] do |c|
  c.desc 'Show tickets accepted'
  c.switch [:a,:accepted]

  c.desc 'Component'
  c.flag [:c,:component]

  c.desc 'Show full description'
  c.switch [:l,:description]

  c.desc 'Arbitrary query (flags are included, too)'
  c.flag [:q,:query]

  c.desc 'Owner ("ME" for the -u user)'
  c.flag [:o,:owner]

  c.desc 'Columns to show'
  c.long_desc 'comma-delimited list of the column names from the tickets to show in the output'
  c.flag [:columns]

  c.action do |global_options,options,args|
    opts = {}

    if options[:q]
      raise "bad query" if options[:q] =~ /\}/
      opts = instance_eval('{' + options[:q] + '}')
    else
      opts[:owner] = options[:o]
      opts[:owner] = global_options[:u] if options[:o] == "ME"
      if options[:a]
        opts[:status] = :accepted
      else
        opts[:status] = ["!closed","testing"]
      end
      opts[:component] = options[:c] if options[:c]
    end
    ids = args
    ids = $trac.tickets.query(opts) if ids.empty?
    tickets = ids.map{ |id| $trac.tickets.get(id) } 
    tickets.sort{ |a,b| a.summary.downcase <=> b.summary.downcase }.each do |ticket|
      if global_options[:noformat]
        puts [ticket.id,ticket.status,ticket.owner,ticket.summary].join("\t")
      else
        columns = options[:columns]
        columns = 'id,status,owner,summary' if !columns
        columns = columns.split(/,/)
        printf_string = ""
        padding = 0
        args = []
        columns.each do |col|
          if col != 'summary'
            size = FORMATTING[col] && FORMATTING[col][:size]
            size = 10 if !size
            padding += size == "" ? 3 : (size.to_i + 3)
            size += 9 if size && FORMATTING[col] && FORMATTING[col][:color]
            printf_string += "%#{size}s - "
            if FORMATTING[col] && FORMATTING[col][:color]
              args << ticket.send(col.to_sym).to_s.color(FORMATTING[col][:color])
            else
              args << ticket.send(col.to_sym).to_s
            end
          end
        end
        printf_string.gsub!(/ - $/,'')
        if columns.include? 'summary'
          args << do_wrap(ticket.summary,padding+1,global_options[:cols])
          printf_string += " - %s"
        end
        printf("#{printf_string}\n",*args)
      end
      if (options[:l])
        description = ticket.description
        if !global_options[:noformat]
          description = bold(description)
          description = italic(description)
          description = code(description)
        end
        puts 
        puts "#{description}"
        puts "----"
      end
    end
  end
end

# Not sure about this
def do_wrap(string,indent,cols)
  cols_left = cols.to_i - indent
  raise "Your terminal is too small for formatting" if (cols_left < 5)
  return_me = ""
  line = ""
  prev_word = ""
  string.split(/\s/).each do |word|
    if line.length <= cols_left
      line += " #{word}" if line != ""
      line += "#{word}" if line == ""
      prev_word = word
    else
      line.gsub!(/#{prev_word}$/,'')
      return_me += line.underline + "\n"
      indent.times { return_me +=  " " }
      line = prev_word
    end
  end
  if (line.length > cols_left)
    line.gsub!(/#{prev_word}$/,'')
    return_me += line.underline + "\n"
    indent.times { return_me +=  " " }
    line = prev_word
  end
  return_me += line.underline
  return_me.gsub(/^\s/,'')
end

def bold(string)
  desc = string.split("'''")
  description = ""
  bold = false
  desc.each do |part|
    description << part.bright.foreground(:cyan) if bold
    description << part.reset if !bold
    bold = !bold
  end
  description
end

def italic(string)
  desc = string.split("''")
  description = ""
  bold = false
  desc.each do |part|
    description << part.italic.foreground(:magenta) if bold
    description << part.reset if !bold
    bold = !bold
  end
  description
end

def code(string)
  desc = string.split("`")
  description = ""
  bold = false
  desc.each do |part|
    description << part.bright.foreground(:green) if bold
    description << part.reset if !bold
    bold = !bold
  end
  in_code = false
  coded = ''
  description.split(/\n/).each do |line|
    ignore = false
    if line =~ /^\{\{\{\s*$/
      in_code = true
      ignore = true
    elsif line =~ /^\}\}\}/
      in_code = false
      ignore = true
    end
    if !ignore
      if in_code
        coded += line.bright.foreground(:green)
      else
        coded += line
      end
    end
    coded += "\n"
  end
  coded
end

pre do |global,command,options,args|
  if command.nil? || command.name == :help
    # not creating a trac instance
  else
    $url = global[:url]
    raise "You must specify a URL" if $url.nil?
    $trac = Trac.new($url,global[:u],global[:p])
  end
  true
end

post do |global,command,options,args|
  # Post logic here
end

on_error do |exception|
  raise exception
  # Error logic here
  # return false to skip default error handling
  true
end

GLI.run(ARGV)
