require 'json'

class App

  def initialize args
    @stab = []
    @detac = []
    @bt = @ofn = nil
    args.each{|arg|
      case arg
      when /^-nsd=/ then @stab.push($')
      when /^-o=/ then @ofn = $'
      when /^-bt=/ then @bt = $'
      else @detac.push(arg)
      end
    }
    @bt = Time.now.utc unless @bt
    @sdb =  {}
    @result = []
    @duptab = {}
  end

  def nwsangle(str)
    case str
    when /^(\d+)-(\d+)[NE]$/ then
      (($1.to_i + $2.to_i / 60.0) * 100 + 0.5).floor * 0.01
    when /^(\d+)-(\d+)[WS]$/ then
      (-($1.to_i + $2.to_i / 60.0) * 100 + 0.5).floor * 0.01
    else nil
    end
  end

  def latlon(la, lo)
    fla = nwsangle(la)
    flo = nwsangle(lo)
    flo += 360 if flo and flo < 0
    return nil unless fla and flo
    [fla, flo]
  end

  def stabload
    if @stab.empty? then
      $stderr.puts 'no station table specified - nsd_bbsss.txt assumed'
      @stab.push 'nsd_bbsss.txt'
    end
    @stab.each {|sfn|
      File.open(sfn, 'r') {|ifp|
        ifp.each_line{|line|
          #47;582;----;Akita;;Japan;2;39-43N;140-06E;39-43N;140-06E;6;21;P^M
          i2,i3,c4,n,st,ct,ra,la,lo,la,lo2,la2,hha,hp,rem = line.chomp.split(/;/,15)
          pos = latlon(la, lo) or next
          @sdb[i2+i3] = { 'name' => n, 'pos' => pos }
        }
      }
    }
  end

  def strtoi str
    case str
    when /^-?\d+$/ then str.to_i
    else nil
    end
  end

  def strtof str
    case str
    when /^-?\d+(\.\d+)?$/ then str.to_f
    else nil
    end
  end

  def vis vv
    iv = strtoi(vv)
    case iv
    when 0 then 50
    when 1..55 then iv * 100
    when 56..80 then (iv - 50) * 1000
    when 81..89 then (iv - 74) * 5000
    when 90 then 25
    when 91 then 50
    when 92 then 200
    when 93 then 500
    when 94 then 1000
    when 95 then 2000
    when 96 then 4000
    when 97 then 10_000
    when 98 then 20_000
    when 99 then 50_000
    else nil
    end
  end

  DAY = 86400

  def mktime dcd
    d = strtoi(dcd['YY'])
    h = strtoi(dcd['GG/9']) || strtoi(dcd['GG'])
    n = strtoi(dcd['gg']) || 0
    unless d and h
      if / (\d\d)(\d\d)(\d\d)/ === dcd['AHL'] then
        d = $1.to_i
        h = $2.to_i
        n = $3.to_i
      else
        $stderr.puts dcd.inspect
        raise
        return 'unknown'
      end
    end
    d = [[d, 31].min, 1].max
    xt = @bt
    # 未来日付の観測はないので、 @bt より遡る
    xt -= DAY while d != xt.day
    xt -= DAY if h == 23 and dcd['GG'] == '0'
    d = xt.day
    m = xt.mon
    y = xt.year
    begin
      return Time.gm(y, m, d, h, n).strftime('%Y-%m-%dT%H:%MZ')
    rescue ArgumentError
      $stderr.puts [y, m, d, h, n].inspect
      return "unknown"
    end
  end

  def synopconv xstnid, h, pos, name
    dd = strtoi(h['dd'])
    dd = nil if dd == 99 or dd == 90
    return nil unless dd
    r = {
      "@" => xstnid,
      "La" => pos[0],
      "Lo" => pos[1],
      "d" => dd * 10
    }
    r['ix'] = h['ix']
    v = vis(h['VV']) ; r['V'] = v if v
    f = strtoi(h['ff'])
    f = Float('%4.1f' % (f * 0.51444)) if f and /[34]/ === h['iw']
    r['f'] = f if f
    n = strtoi(h['N'])
    r['N'] = (n * 12.5).floor if n
    w = strtoi(h['ww'])
    r['w'] = w if w
    t = strtoi(h['TTT'])
    r['T'] = Float('%4.1f' % (t * 0.1 + 273.15)) if t
    t = strtoi(h['Td.3'])
    r['Td'] = Float('%4.1f' % (t * 0.1 + 273.15)) if t
    p4 = strtoi(h['P.4'])
    r['P'] = p4 * 10 if p4
    p0 = strtoi(h['P0.4'])
    r['P0'] = p0 * 10 if p0
    cl = strtoi(h['CL']) ; r['CL'] = cl if cl
    cm = strtoi(h['CM']) ; r['CM'] = cm if cm
    ch = strtoi(h['CH']) ; r['CH'] = ch if ch
    r['#'] = name if name
    r['ahl'] = h['AHL'] if h['AHL']
    r
  end

  def tempconv xstnid, h, pos
    keys = h.keys.grep(/^dd@/).map{|s| s.sub(/^dd@/, '')}
    keys.each{|pl|
      r = {
        "@" => xstnid,
        "La" => pos[0],
        "Lo" => pos[1],
        'd' => strtoi(h["dd@#{pl}"])
      }
      sel = "fff@#{pl}"
      r['f'] = strtof(h[sel]) if h.include? sel
      sel = "hhh@#{pl}"
      r['z'] = strtoi(h[sel]) if h.include? sel
      t = strtoi(h["TTTa@#{pl}"])
      r['T'] = Float('%4.1f' % (t * 0.1 + 273.15)) if t
      sel = "DD@#{pl}"
      dd = strtoi(h[sel])
      if dd and r['T'] then
        r['Td'] = Float('%4.1f' % (r['T'] - dd * 0.1))
      end
      yield(pl.sub(/SURF/, 'sfc'), r)
    }
  end

  def detacload
    @detac.each {|detac|
      File.open(detac, "r:ASCII-8BIT") {|ifp|
        ifp.each_line{|line|
          h = {}
          line.chomp.split(/\t/).each{|kvp|
            k, v = kvp.split(/:/, 2)
            h[k] = v
          }
          stnid = h['stnid'] = h['@ID'].to_s
          next if stnid.empty?
          xstnid = pos = name = nil
          case h['@MiMj']
          when 'AAXX', 'TTAA' then
            unless @sdb[stnid]
              $stderr.puts "fixed #{stnid} unlocatable"
              next
            end
            xstnid = stnid
            pos = @sdb[stnid]['pos']
            name = @sdb[stnid]['name']
          else
            xstnid = "v#{stnid}"
            xstnid = "m#{stnid}" if 'IIAA' == h['@MiMj']
            lat = strtoi(h['La.3'])
            lon = strtoi(h['Lo.4'])
            unless lat and lon
              $stderr.puts "mobile #{stnid} unlocatable"
              next
            end
            lat *= 0.1
            lon *= 0.1
            lon += 360 if lon < -30
            pos = [lat, lon]
          end
          key = [mktime(h), 'sfc', xstnid].join('/')
          if stnid != 'SHIP' then
            if @duptab[key]
              $stderr.puts "station #{key} dup"
              next
            end
            @duptab[key] = 1
          end
          case h['@MiMj']
          when 'TTAA','UUAA','IIAA' then
            tempconv(xstnid, h, pos) {|pl, r|
              key2 = [mktime(h), pl, xstnid].join('/')
              @result.push [key2, r]
            }
          else
            r = synopconv(xstnid, h, pos, name)
            next unless r
            @result.push [key, r]
          end
        }
      }
    }
  end

  def saveto ofp
    for key, ent in @result
      json = ent.to_json.gsub(/\B000000+\d\b/, '')
      ofp.puts([key, json].join(' '))
    end
  end

  def output
    if @ofn
    then
      File.open(@ofn, "w"){|ofp| saveto(ofp) }
    else
      saveto($stdout)
    end
  end

  def run
    stabload
    detacload
    output
  end

end

App.new(ARGV).run