#!/usr/bin/env ruby
require 'open-uri'
require 'tempfile'
require 'fileutils'
require 'time'
require 'mechanize'
require 'hpricot'

class File
  def self.write(path, str, perm=nil)
    File.open(path, "wb") do |f|
      f.chmod perm if perm
      f.write str
    end
  end
end

class SPOJHelper
  Status = Struct.new(:id, :date, :problem, :result, :time, :mem, :lang)

  def initialize(code)
    @code = code
    @config_path = File.expand_path("~/.spoj_helper")
  end

  def do_play
    get_inout if [infile(@code), outfile(@code)].any?{|path| not File.exist?(path)}
    make_rb    unless File.exist? rbfile(@code)

    puts "ready for playing #{@code}."
    puts "edit #{rbfile(@code)} and run 'spoj test #{@code}'. enjoy! :-)"
  end

  def do_test
    puts "---actually:"
    temp = Tempfile.new("spoj"); temp.close
    system "ruby #{rbfile(@code)} < #{infile(@code)} | tee #{temp.path}"
    actually = File.read(temp.path)
    expected = File.read(outfile(@code))

    if expected == actually
      puts
      puts "test passed! :-)"
      if input("submit now? [y/n]") == "y"
        do_submit
      end
    else
      puts "---expected:"
      puts expected
      puts 
      puts "---diff:"
      puts `diff -c #{temp.path} #{outfile(@code)}`
    end
  end

  def do_submit
    puts "initializing..."
    load_config
    @agent = WWW::Mechanize.new
    last_status = get_status

    page = @agent.get("http://www.spoj.pl/submit/#{@code}/")
    form = page.forms.action("/submit/complete/").first
    lang = form.fields.name('lang').first
    loop do
      option = lang.options.find{|o| o.text =~ /#{@lang}/i}
      if option
        form.login_user = @user
        form.password   = @pass
        lang.value = option.value
        form.file_uploads.name('subm_file').first.file_name = rbfile(@code)
        form.problemcode = @code
        submit_time = Time.now
        puts "sending #{rbfile(@code)}..."
        res_page = @agent.submit(form)

        if show_responce(res_page) == :ok
          show_result(last_status)
        end
        break
      else
        choose_lang
      end
    end
  end

  private
  def infile(code)
    "in_#{code.downcase}"
  end

  def outfile(code)
    "out_#{code.downcase}"
  end

  def rbfile(code)
    "#{code.downcase}.rb"
  end

  def get_inout
    @agent ||= WWW::Mechanize.new
    page = @agent.get("http://www.spoj.pl/problems/#{@code}/")
    h3 = (page.root/:h3).find{|p| p.inner_html == "Example"}

    if h3.next_sibling.name =~ /pre|div/
      text = h3.next_sibling.inner_text
      text = text.split(/^.*input.*\n/i).slice(1..-1).join
      input, output, = text.split(/^.*output.*\n/i)
    elsif h3.next_sibling.name == "p"
      p1 = h3.next_sibling
      input = p1.next_sibling.inner_text
      p2 = p1.next_sibling.next_sibling
      output = p2.next_sibling.inner_text
    else 
      raise "sorry, can't parse #{@code}'s sample input/output."
    end

    File.write(infile(@code),  clean(input))
    File.write(outfile(@code), clean(output))
    puts "wrote #{infile(@code)}, #{outfile(@code)}"
  end

  def clean(str)
    str.strip.split(/\r?\n/).map{|line| line.rstrip + "\n"}.join
  end

  def make_rb
    path = rbfile(@code)
    puts "missing #{path}..."

    FileUtils.touch(path)
    puts "wrote #{path}"
  end

  def input(prompt)
    print "#{prompt} : "
    $stdin.gets.chomp
  end

  def load_config
    if File.exist?(@config_path)
      @user, @pass, @lang, = File.readlines(@config_path).map{|l| l.chomp}
    else
      make_config
    end
  end

  def save_config
    File.write(@config_path, "#{@user}\n#{@pass}\n#{@lang}", 0600)
    puts "wrote #{@config_path}"
  end

  def make_config
    puts "#{@config_path} does not exist..."
    loop do
      @user = input("user name")
      @pass = input("password")
      @lang = input("favorite programming language")
      puts "user : #{@user}  pass : #{@pass}  lang : #{@lang}"
      break if input("ok? [y/n]") == "y"
    end
    save_config
  end

  def choose_lang
    puts "language #{@lang} not found."
    @lang = input("new language")
    save_config
  end

  def show_responce(page)
    if page.root.at(:h3).inner_text == "Solution submitted!"
      puts "submitted."
      :ok
    elsif page.root.at(:title).inner_text =~ /Authorisation required/
      puts "bad username or password."
      :ng
    else
      puts "unknown responce:"
      puts page.root.to_html
      :ng
    end
  end

  def get_status
    page = @agent.get("http://www.spoj.pl/status/#{@code},#{@user}/")
    if (tr = page.root.at("table.problems tr:nth-child(1)"))
      parse_status(tr)
    else
      nil
    end
  end

  def parse_status(tr)
    id, date, problem, result, time, mem, lang = (tr/:td).map{|td| td.inner_text.strip}
    date    = Time.parse(date)
    Status.new(id, date, problem, result, time, mem, lang)
  end

  def show_result(last_status)
    print "waiting result.." 
    status = nil
    loop do
      print "."; $stdout.flush
      status = get_status
      break if updated?(last_status, status)
      sleep 2
    end
    puts
    puts <<-EOD

result  : * #{status.result} *
id      : #{status.id}
date    : #{status.date}
problem : #{status.problem}
time    : #{status.time}
mem     : #{status.mem}
lang    : #{status.lang}
    EOD
  end

  def updated?(last_status, status)
    status and (last_status.nil? || last_status.id != status.id) and status.result !~ /compiling|running/
  end

end

USAGE = <<EOD
usage: spoj [command]
command:
  play CODE : generate code.rb, in_code, out_code
  test CODE : ruby code.rb < in_code | diff out_code
  submit CODE : submit code.rb
EOD

if ARGV.size < 2
  puts USAGE
else
  cmd  = ARGV[0]
  code = ARGV[1].upcase
  do_cmd = "do_#{cmd}"
  helper = SPOJHelper.new(code)

  if helper.respond_to? do_cmd
    helper.__send__(do_cmd)
  else
    puts "error: no such command: #{cmd}"
  end
end

# vim:set ft=ruby:


  
