#!/usr/bin/env ruby
require 'rubygems'
require 'builder'
require 'mongrel'
require 'mechanize'



ANN_BALANCE_SHEET = "http://finance.yahoo.com/q/bs?annual&s="
Q_BALANCE_SHEET = "http://finance.yahoo.com/q/bs?s="
KEY_ST = "http://finance.yahoo.com/q/ks?s="
ANN_CASH_FLOW = "http://finance.yahoo.com/q/cf?annual&s="
Q_CASH_FLOW = "http://finance.yahoo.com/q/cf?s="
ANN_INCOME_STATEMENT = "http://finance.yahoo.com/q/is?annual&s="
Q_INCOME_STATEMENT = "http://finance.yahoo.com/q/is?s="

FIELDS = %w(
name
ticker
ev
ann_ebit
ann_total_assets
ann_current_liabilities
ann_da
ann_roc
ann_ey
q_ebit
q_total_assets
q_current_liabilities
q_da
q_roc
q_ey
latest_q
)
class Stock
  def initialize(symbol)
    @symbol = symbol
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac Safari'
    @ann_balance_sheet = @agent.get(ANN_BALANCE_SHEET + @symbol)
    @q_balance_sheet = @agent.get(Q_BALANCE_SHEET + @symbol)
    @key_st = @agent.get(KEY_ST + @symbol)
    @ann_cash_flow = @agent.get(ANN_CASH_FLOW + @symbol)
    @q_cash_flow = @agent.get(Q_CASH_FLOW + @symbol)
    @ann_income_statement = @agent.get(ANN_INCOME_STATEMENT + @symbol)
    @q_income_statement = @agent.get(Q_INCOME_STATEMENT + @symbol)
  end  

  def name(h)
    @name = @key_st.search("/html/head/title").inner_text.sub(/.*Key Statistics for ([^-]+) - Yahoo.*/, '\1')
    h.td {
      h.a(@name, :href => "#{KEY_ST}#{@symbol}")
    }  
  end

  def ticker
    @symbol
  end

  def ev
    if (not(@ev))
      tmp_ev = @key_st.search("//tr").inner_html.sub(/.*Enterprise Value .\d[^<]+<font size=.-1.><sup>3<.sup><.font>:<.td><td class=.yfnc_tabledata1.>([^<]+)<.td>.*/mi, '\1')
      last_char = tmp_ev[-1].chr
      tmp_ev = tmp_ev[0..-2].to_f
      if last_char == "M"
        tmp_ev = tmp_ev * 1000
      elsif last_char == "B"
        tmp_ev = tmp_ev * 1000000
      end
      @ev = tmp_ev
    end
    return @ev
  end

  def ann_ebit
    if (not(@ann_ebit))
      @ann_ebit = @ann_income_statement.search("//tr").inner_html.sub(/.*<td>Income Before Tax<.td><td align="right">([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @ann_ebit
  end
  
  def ann_total_assets
    if (not(@ann_total_assets))
      @ann_total_assets = @ann_balance_sheet.search("//tr").inner_html.sub(/.*<b>Total Assets<.b><.td><td align="right"><b>([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @ann_total_assets
  end
  
  def ann_current_liabilities
    if (not(@ann_current_liabilities))
      @ann_current_liabilities = @ann_balance_sheet.search("//tr").inner_html.sub(/.*<b>Total Current Liabilities<.b><.td><td align="right"><b>([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @ann_current_liabilities
  end
  
  def ann_da
    if (not(@ann_da))
      @ann_da = @ann_cash_flow.search("//tr").inner_html.sub(/.*<td colspan="2">Depreciation<.td><td align="right">([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @ann_da
  end
  
  def ann_roc
    #EBIT/(TA-CL-D&A)
    ebit = to_num(ann_ebit()).to_f
    ann_roc =  (ebit / (to_num(ann_total_assets()) - to_num(ann_current_liabilities()) - to_num(ann_da()))) * 100
    return (sprintf("%4.3f", ann_roc) + "%")
  end
  
  def ann_ey
    ebit = to_num(ann_ebit())
    ann_ev =  (ebit / ev()) * 100
    return (sprintf("%4.3f", ann_ev) + "%")
  end

  def q_ebit
    if (not(@q_ebit))
      @q_ebit = @q_income_statement.search("//tr").inner_html.sub(/.*<td>Income Before Tax<.td><td align="right">([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @q_ebit
  end
  
  def q_total_assets
    if (not(@q_total_assets))
      @q_total_assets = @q_balance_sheet.search("//tr").inner_html.sub(/.*<b>Total Assets<.b><.td><td align="right"><b>([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @q_total_assets
  end
  
  def q_current_liabilities
    if (not(@q_current_liabilities))
      @q_current_liabilities = @q_balance_sheet.search("//tr").inner_html.sub(/.*<b>Total Current Liabilities<.b><.td><td align="right"><b>([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @q_current_liabilities
  end
  
  def q_da
    if (not(@q_da))
      @q_da = @q_cash_flow.search("//tr").inner_html.sub(/.*<td colspan="2">Depreciation<.td><td align="right">([^<]+).*/mi, '\1').gsub(/.nbsp;/, '')
    end
    return @q_da
  end
  
  def q_roc
    #EBIT/(TA-CL-D&A)
    ebit = to_num(q_ebit()).to_f
    q_roc =  (ebit / (to_num(q_total_assets()) - to_num(q_current_liabilities()) - to_num(q_da()))) * 100
    return (sprintf("%4.3f", q_roc) + "%")
  end
  
  def q_ey
    ebit = to_num(q_ebit())
    q_ev =  (ebit / ev()) * 100
    return (sprintf("%4.3f", q_ev) + "%")
  end

  def latest_q
   return @q_income_statement.search("//tr").inner_html.sub(/.*<b>PERIOD ENDING<.b><.small><.TD><TD class="yfnc_modtitle1" align="right"><b>([^<]+)<.b>.*/im, '\1')
  end

  private 
  def to_num(arg)
    arg.gsub!(/,/, '')
    if arg =~ /^\(/
      arg = arg[1..-2]
    elsif  arg =~ /       -/ 
      arg = "0"
    end
    return arg.to_i
  end
end

class MagicHandler < Mongrel::HttpHandler
  PORT = 4115
  SERVER = "0.0.0.0"
  def process(request, response)
    begin
      query = Mongrel::HttpRequest::query_parse(request.params["QUERY_STRING"])
      response.start(200) {|head, out|
        head["Content-Type"] = "text/html"
        builder = Builder::XmlMarkup.new(:target=>out) #, :indent=>2)
        # so that we could validate this in the future...
        builder.declare! :DOCTYPE, :html, :PUBLIC, "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
        builder.html(:xmlns => "http://www.w3.org/1999/xhtml", :"xml:lang" => "en") {|h|
          h.head {
            h.meta(:"http-equiv" => "Content-Type", :content => "text/html; #charset=utf-8")
            h.title("Sybaris Research Report")
          }  

          # delegate to a separate method for each subsection
          h.body {
            if query.has_key?("symbols")
              h.table(:border => "1") {
                FIELDS.each {|column_name|
                  column_prettyname = column_name.to_s.split('_').map {|x| x.capitalize}.join(' ')
                  h.th(column_prettyname)
                }  
                query["symbols"].split(",").each {|s|
                  sym = CGI::unescape(s)
                  stock = Stock.new(sym)
                  h.tr {
                    FIELDS.each {|func_name|
                      if func_name == "name"
                        stock.name(h)
                      else
                        val = stock.send(func_name.to_sym)
                        h.td(val)
                      end
                    }  
                  }
                }
              }
            else
              h.p("No symbols")
            end 
          }  
        }
      }
    rescue => e
      return response.start(500) {|head, out| out.print "server error (#{e})"}
    end
  end
end



h = Mongrel::HttpServer.new(MagicHandler::SERVER, MagicHandler::PORT)
h.register("/magic", MagicHandler.new)
puts "Started server on #{MagicHandler::SERVER}:#{MagicHandler::PORT}"
trap("INT"){ h.stop }
h.run.join
