require 'webrick'

if $DEBUG
  Thread.abort_on_exception
else
  exit if fork
  $stdout = File.new('/dev/null', 'w')
  $stderr = File.new('/dev/null', 'w')
end

Thread.new do
  while(true)
    sleep(300)
    GC.start
  end
end

class String
  def underscore
    self.gsub(/::/, '/').
     gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
     gsub(/([a-z\d])([A-Z])/,'\1_\2').
     tr("-", "_").
     downcase
  end
  alias_method :to_json, :inspect
end

class Numeric
  def formatted(precision = 1)
    rounded_number = (Float(self) * (10 ** precision)).round.to_f / 10 ** precision
    parts = ("%01.#{precision}f" % rounded_number).to_s.split('.')
    parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
    parts.join(".")
  end
  alias_method :to_json, :inspect
end

class Array
  def to_json
    "[#{map { |e| e.to_json }.join(',')}]"
  end
end

class Hash
  def formatted
    out = self.dup
    out.each_pair { |k, v| out[k] = v.formatted }
    out
  end
  def to_json
    arr = []
    each_pair { |k, v| arr << "#{k.to_json}:#{v.to_json}" }
    "{#{arr.join(',')}}"
  end
end

class Symbol
  def to_json
    to_s.inspect
  end
end

module DataProviders
  DATA_SOURCES = {}
  def self.setup
    Dir.glob("#{File.dirname(__FILE__)}/data_providers/*.rb").each { |file| load file }
    DataProviders.constants.each do |c|
      c = DataProviders.const_get(c)
      DATA_SOURCES[c.to_s.gsub(/^DataProviders::/, '').underscore] = c.new if c.is_a? Class
    end
  end
end

DataProviders.setup

class Webstats < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, res)
    WEBrick::HTTPAuth.basic_auth(req, res, "Webstats") { |user, pass| user == 'webstats' and pass == ARGV[0] } unless ARGV.empty?

    body = ""
    if req.path_info == '/'
      body << <<-EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Webstats</title>
    <style type="text/css">
      * { margin: 0; padding: 0; font-family: "Lucida Grande", Helvetica, Arial, sans-serif; font-size: 100%; }
      body { font-size: 95%; }
      p { margin: 0 0 1em 0; }

      h1 { margin: 1em; }
      h1 span { font-size: 160%; font-weight: bold; }
      .source .danger { background-color: #FF0D33; }
      .source .warning { background-color: #F1FF28; }

      .source { width: 500px; border: 1px solid #000000; margin: 1em; }
      .source h2 { padding: 0 0.8em 0 0.8em; background-color: #C98300; }
      .source h2 span { font-size: 130%; font-weight: bold; padding: 0.2em 0; display: block; }
      .source .source_contents { padding: 0.8em; }
      .source .title { padding-right: 0.5em; }
      .source .major_figure { font-size: 130%; margin: 0.3em 0; }
      .source .major_figure .figure { font-size: 120%; font-weight: bold; font-family: Georgia, serif; }
      .source .major_figure .unit { font-family: Georgia, serif; font-size: 70%; }
      .source .minor_figure { font-family: Georgia, serif; }
      .source .divider { margin-left: 0.2em; margin-right: 0.2em; font-weight: normal; }
    </style>
    <script type="text/javascript">
       var http = null;

       function getLatest()
       {
          http.open("get", "/update", true);
          http.send(null);
       }

       window.onload = function()
       {
          http = !!(window.attachEvent && !window.opera) ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();

          http.onreadystatechange = function()
          {
            if(http.readyState == 4)
            {
               var results = eval("(" + http.responseText + ")");
               if(!results) return;
EOF

      DataProviders::DATA_SOURCES.each_pair do |k, v|
        body << %{var data_source = results['#{k}']; var sc = document.getElementById('source_contents_#{k}'); sc.className = "source_contents " + (data_source['status'] ? data_source['status'] : ''); #{v.renderer[:contents]}\n}
      end

body << <<-EOF
            }
         };

         window.setInterval("getLatest()", 5000);
         getLatest();
      }
    </script>
  </head>
  <body id="body">
    <div id="main">
      <h1><span>Stats for #{req.host}</span></h1>
EOF
      DataProviders::DATA_SOURCES.sort { |a, b| b[1].importance <=> a[1].importance }.each do |(k, v)|
        r = v.renderer
        body << %{<div class="source" id="source_#{k}"><h2><span>#{r[:name]}</span></h2><div class="source_contents" id="source_contents_#{k}">Loading...</div></div>}
      end

      body << <<-EOF
    </div>
  </body>
</html>
EOF
    elsif req.path_info == '/update'
      out = {}
      DataProviders::DATA_SOURCES.each_pair do |k, v|
        out[k] = v.get
      end

      fix_leaves_hash(out)

      body << out.to_json
    elsif req.path_info == '/information'
      out = {}
      DataProviders::DATA_SOURCES.each_pair { |k, v| out[k] = v.information }
      body << out.to_json
    end

    res.body = body
    res['Content-Type'] = "text/html"
  end

  private
  def fix_leaves_array(array)
    array.each_with_index do |v, i|
      if v.is_a? Numeric
        array[i] = v.formatted
      elsif v.is_a? Hash
        array[i] = fix_leaves_hash(array[i])
      elsif v.is_a? Array
        array[i] = fix_leaves_array(array[i])
      end
    end
  end

  def fix_leaves_hash(hash)
    hash.each_pair do |k, v|
      if v.is_a? Numeric
        hash[k] = v.formatted
      elsif v.is_a? Hash
        hash[k] = fix_leaves_hash(hash[k])
      elsif v.is_a? Array
        hash[k] = fix_leaves_array(hash[k])
      end
    end
  end
end

s = WEBrick::HTTPServer.new(:Port => 9970)

death = proc do
  s.shutdown
  DataProviders::DATA_SOURCES.each_pair { |k, v| v.kill }
end
trap("INT", death)
trap("TERM", death)

s.mount("/", Webstats)
s.start