class HTMLParser

  def initialize str
    @s = StringScanner.new(str)
  end

  def run
    while not @s.eos?
      if decl = @s.scan(/<![^>]+>/)
	next
      elsif tag = @s.scan(/<(\w+)((?: [-\w]+=(?:"[^"]+"|\S+))*)>/)
        as = {}
	a = StringScanner.new(@s[2])
	while not a.eos?
	  if a.scan(/\s+/) then next
	  elsif a.scan(/([-\w]+)=(?:"([^"]+)")/)
	    as[a[1].downcase] = a[2]
	  elsif a.scan(/([-\w]+)=(\S+)/)
	    as[a[1].downcase] = a[2]
	  end
	end
	yield :opentag, @s[1].downcase, as
      elsif tag = @s.scan(/<\/(\w+)>/)
	yield :closetag, @s[1].downcase
      elsif @s.scan(/<!--[^>]*-->/)
	next
      elsif text = @s.scan(/[^<]+/)
	a = StringScanner.new(text)
	buf = []
	while not a.eos?
	  if str = a.scan(/[^&]+/)
	    buf.push str
	  elsif a.scan(/&nbsp;?/) 
	    buf.push "\xC2\xA0"
	  else
	    buf.push a.scan(/./)
	  end
	end
	yield :text, buf.join
      else
	text = @s.scan(/<[^>]*>/)
	yield :text, text
      end
    end
  end

end

class WebTabParser

  def initialize host, port, path
    @host, @port, @path = host, port, path
    @cond = @row_post = nil
  end

  def newtab_cond(&cond)
    @cond = cond
  end

  def row_post(&sub)
    @row_post = sub
  end

  def run
    tab = []
    require 'net/http'
    require 'strscan'
    Net::HTTP.version_1_2
    Net::HTTP.start(@host, @port) {|http|
      resp = http.get(@path)
      stack = []
      row = nil
      colspan = nil
      rowspan = {}
      text = []
      HTMLParser.new(resp.body).run {|ttype, tval, as|
        $deferr.puts "[#{ttype}, #{tval}]" if $DEBUG
	case ttype
	when :opentag
	  case tval
	  when 'td', 'th' then
	    if stack.size == 2
	      stack.push :cell
	      text = []
	      $deferr.puts "#cell" if $DEBUG
	      colspan = as['colspan'].to_i if as['colspan']
	      rowspan[row.size] = -as['rowspan'].to_i if as['rowspan']
	    end
	  when 'tr' then
	    if stack.size >= 2
	      for icol in rowspan.keys.sort.reverse
	        if rowspan[icol] < 0 then
		  rowspan[icol] = -rowspan[icol] 
		else
		  row[icol,0] = ['||']
		  rowspan[icol] -= 1
		  $deferr.puts "#rowspan #{icol}" if $DEBUG
		  rowspan.delete(icol) if rowspan[icol] <= 1
		end
	      end
	      @row_post.call(row) if @row_post
	      tab.push row
	      row = []
	      $deferr.puts "#endrow-#row" if $DEBUG
	    elsif stack.size == 1
	      stack.push :row
	      row = []
	      $deferr.puts "#row" if $DEBUG
	    end
	  when 'table' then
	    if @cond ? @cond.call(ttype, tval, as) : true
	      stack.push :table
	      $deferr.puts "#tab" if $DEBUG
	    end
	  when 'br' then
	    text.push " "
	  end
	when :closetag
	  case tval
	  when 'td', 'th' then
	    if stack.size >= 3
	      stack = stack[0,2]
	      row.push text.join
	      if colspan then
	        row << ([row.last] * (colspan - 1))
		colspan = nil
	      end
	      $deferr.puts "#endcell" if $DEBUG
	    end
	  when 'tr'
	    if stack.size >= 2
	      stack = stack[0,1]
	      for icol in rowspan.keys.sort
	        if rowspan[icol] < 0 then
		  rowspan[icol] = -rowspan[icol] 
		else
		  row[icol,0] = ['||']
		  rowspan[icol] -= 1
		  $deferr.puts "#rowspan #{icol}" if $DEBUG
		  rowspan.delete(icol) if rowspan[icol] <= 1
		end
	      end
	      @row_post.call(row) if @row_post
	      tab.push row
	      $deferr.puts "#endrow" if $DEBUG
	    end
	    stack.pop if stack.last == :row
	  when 'table'
	    unless stack.empty?
	      stack = []
	      $deferr.puts "#endtab" if $DEBUG
	    end
	  end
	when :text
	  text.push tval if stack.size == 3
	end
      }
    }
    tab
  end

end

class Amd

  def initialize parent, relpath
    @parent = parent
    @path = relpath
  end

  def stnlist
    tab = []
    File.open(File.join(App::DATADIR, 'stnhack.csv'), 'r') { |fp|
      for line in fp
        id, wid, name = line.chomp.split(/,/)
	next if /^#id/ === id
        row = [id, name]
        tab.push row
      end
    }
    @parent.list tab, :show_any => true
  end

  def resolve_station stn
    File.open(File.join(App::DATADIR, 'stnhack.csv'), 'r') { |fp|
      for line in fp
        id, wid, name = line.chomp.split(/,/)
	next unless stn == id
	return wid.split(/-/, 2)
      end
    }
  end

  def menu1 stn
    prec, wid = resolve_station(stn)
    if stn == 'any' then
      lst = [
	['stnmeta', '地点情報']
      ]
    elsif /^\d\d\d\d\d$/ === wid then
      lst = [
	['stnmeta', '地点情報'],
	['monthly', '月毎の値'],
	['normal-monthly', '月毎の平年値'],
      ]
    else
      lst = [
	['stnmeta', '地点情報'],
	['normal-monthly', '月毎の平年値'],
      ]
    end
    @parent.list lst
  end

  def monthly stn
    @parent.list [
      ['a1', '日平均気温'],
      ['a2', '日最高気温'],
      ['a3', '日最低気温'],
      ['a4', '平均風速'],
      ['a5', '海面気圧'],
      ['a6', '現地気圧'],
      ['a7', '相対湿度'],
      ['a8', '蒸気圧'],
      ['a9', '雲量'],
      ['a10', '日照率'],
      ['a11', '全天日射量'],
      ['a12', '日照時間'],
      ['a13', '降水量'],
      ['a14', '降雪の深さ'],
    ], :linkto => 'data'
  end

  def monthly_data stn, elem
    prec, wstn = resolve_station(stn)
    path = "/obd/stats/etrn/view/monthly_s3.php?prec_no=#{prec}&block_no=#{wstn}&year=&month=&day=&view=#{elem}"
    wtp = WebTabParser.new('www.data.jma.go.jp', 80, path)
    wtp.newtab_cond {|ttype, tval, as| as['class']}
    tab = wtp.run
    @parent.table tab
  end

  def stnmeta stn
    @parent.list [
      ['history', '履歴'],
      ['latest', '最新のみ'],
    ], :linkto => 'data'
  end

  def stnmeta_data stn, squeeze
    File.open(File.join(App::DATADIR, 'amdmaster.csv'), 'r') { |fp|
      head = nil
      tab = (squeeze == 'latest') ? Hash.new : Array.new
      for line in fp
        row = line.chomp.split(/,/)
	if not head then
	  head = row
	else
	  id = row[0]
	  if stn == 'any' or stn == id then
	    namej = row[1]
	    namee = row[3]
	    lat = row[4].to_f + row[5].to_f / 60.0
	    lon = row[6].to_f + row[7].to_f / 60.0
	    hgt = row[8].to_f
	    begdate = sprintf('%04s-%02s-%02s', row[15], row[16], row[17])
	    enddate = sprintf('%04s-%02s-%02s', row[18], row[19], row[20])
	    row = [id, namej, namee, lat, lon, hgt, begdate, enddate]
	    case tab
	    when Hash
	      next unless /^9999-/ === enddate
	      tab[id] = row
	    when Array
	      tab.push row
	    end
	  end
	end
      end
      if Hash === tab
        tab = tab.keys.map{|id| tab[id]}
      end
      @parent.table tab
    }
  end

  def normal_monthly stn
    prec, wid = resolve_station(stn)
    case wid
    when /^\d\d\d\d$/ then
      @parent.list [
	['p1', '主な要素'],
	['a1', '詳細(降水量)'],
      ], :linkto => 'data'
    when /^\d\d\d\d\d$/ then
      @parent.list [
	['p1', '主な要素'],
	['a1', '詳細(気圧・降水量) ... 予定'],
      ], :linkto => 'data'
    end
  end

  def normal_monthly_data_amd(stn, type, prec, wid)
    path = "/obd/stats/etrn/view/nml_amd_ym.php?prec_no=#{prec}&block_no=#{wid}&year=&month=&day=&view="
    wtp = WebTabParser.new('www.data.jma.go.jp', 80, path)
    wtp.newtab_cond {|ttype, tval, as| as['class']}
    wtp.row_post {|row|
      case [type, row.first]
      when ['a1', '合計'] then  row.unshift('')
      when ['a1', '≧1.0mm'] then  row.unshift('', '')
      end
    }
    tab = wtp.run
    @parent.table tab
  end

  def normal_monthly_data_sfc(stn, type, prec, wid)
    path = "/obd/stats/etrn/view/nml_sfc_ym.php?prec_no=#{prec}&block_no=#{wid}&year=&month=&day=&view=#{type}"
    wtp = WebTabParser.new('www.data.jma.go.jp', 80, path)
    wtp.newtab_cond {|ttype, tval, as| as['class']}
    wtp.row_post {|row|
      case [type, row.first]
      when ['a1', '合計'] then  row.unshift('')
      end
    }
    tab = wtp.run
    @parent.table tab
  end

  def normal_monthly_data stn, type
    prec, wid = resolve_station(stn)
    case wid
    when /^\d\d\d\d$/ then normal_monthly_data_amd(stn, type, prec, wid)
    when /^\d\d\d\d\d$/ then normal_monthly_data_sfc(stn, type, prec, wid)
    end
  end

  def run
    case @path
    when %r{^/*(?:index(?:\.\w+)?)?$}
      stnlist
    when %r{^/(\w+)/*(?:index(?:\.\w+)?)?$}
      menu1 $1
    when %r{^/(\w+)/stnmeta/*(?:index(?:\.\w+)?)?$}
      stnmeta $1
    when %r{^/(\w+)/stnmeta/(\w+)/*(?:data(?:\.\w+)?)?$}
      stnmeta_data $1, $2
    when %r{^/(\w+)/monthly/*(?:index(?:\.\w+)?)?$}
      monthly $1
    when %r{^/(\w+)/monthly/(\w+)/*(?:data(?:\.\w+)?)?$}
      monthly_data $1, $2
    when %r{^/(\w+)/normal-monthly/*(?:index(?:\.\w+)?)?$}
      normal_monthly $1
    when %r{^/(\w+)/normal-monthly/(\w+)/*(?:data(?:\.\w+)?)?$}
      normal_monthly_data $1, $2
    else
      raise HTTP404
    end
  end

end