#! /usr/bin/env ruby
#
# ruby script to convert from gtool3 to netCDF
# 
# created by Noriyoshi Takahasi
# modified by S Takehiro (2006/01/05)
# modified by Seiya Nishizawa
# last modified on 09 May 2007 by Seiya Nishizawa
#
#


require "narray"
require "numru/netcdf"

include NumRu

class File

  @endian = :little

  def fuget(atype, *opts)
    @no_strip = false

    if !opts.nil? then
      opts.each {|opt|
	@no_strip = true  if opt == :no_strip
	@endian = :big    if opt == :big_endian
	@endian = :little if opt == :little_endian
      }
    end

    ret = Array.new

    if atype.class != Array then
      raise TypeError, "an array expected, got #{array.class}"
    end

    if (self.read(4).nil?) then
      return nil
    end

    atype.each {|a|

      if a.class != Array || a.size < 2 then
	raise TypeError, "[\"type\", size(, byte)] Array expected."
      end

      if a[1].nil? || (a[1] == 0) then
	raise RangeError, "Please set size > 0."
      else
	dsize = a[1].to_i 
      end
      case a[0]
      when "char", "character"
	type = "C"
	byte = a[2].nil? ? 1 : a[2]
      when "short"
	type = (@endian == :little) ? "v" : "n"
	byte = a[2].nil? ? 2 : a[2]
      when "int", "integer", "long"
	type = (@endian == :little) ? "V" : "N"
	byte = a[2].nil? ? 4 : a[2]
      when "float", "real"
	type = (@endian == :little) ? "e" : "g"
	byte = a[2].nil? ? 4 : a[2]
      when "double"
	type = (@endian == :little) ? "E" : "G"
	byte = a[2].nil? ? 8 : a[2]
      else
	type = "C"
	byte = a[2].nil? ? 1 : a[2]
      end

      ary = Array.new

      case type
      when "C"
	if @no_strip then
	  ary.push self.read(dsize*byte)
	else
	  ary.push self.read(dsize*byte).strip
	end
	ary = ary[0]
      else
	(0...dsize).each do |i|
	  ary.push self.read(byte).unpack(type)[0]
	end
	ary = NArray[ary][true,0]
      end
      ret.push ary

    }

    self.read(4)

    return (ret.size == 1) ? ret[0] : ret

  end

  def set_endian(endian)

    if endian == :big || endian == :little then
      @endian = endian
    else
      raise NameError, "Unknown endian type : #{endian}"
    end

  end

end

class Gt3nc

  class Axis

    attr_accessor :short_name, :long_name, :unit,
      :cyclic, :dmin, :dmax, :divs, :divl, :positive, :log, :dstr, :dend
    attr_reader :data_type

    def initialize(axis, path, *opts)

      floc = "GTAXLOC." + axis["name"]
      fwgt = "GTAXWGT." + axis["name"]
      @min = axis["min"]
      @max = axis["max"]

      @floc = File.open(path + "/" + floc, "rb")
      begin
	@fwgt = File.open(path + "/" + fwgt, "rb")
      rescue

      end

      if (opts[0].key? :endian) then
	@floc.set_endian(opts[0][:endian])
	@fwgt.set_endian(opts[0][:endian]) if @fwgt
      end

      hl = Array.new

      64.times do
	hl.push ["character", 16]
      end

      @idfm, @dset, @item,
	tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp,
	tmp, tmp, title0, title1, @unit,
	tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp,
	tmp, tmp, tmp, tmp,
	tmp, @dstr, @dend, tmp, tmp, tmp, tmp, tmp, tmp,
	dfmt, @miss, @min, @max, @divs, @divl, styp,
	tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp, tmp,
        tmp, tmp, tmp, tmp, tmp, tmp, tmp, @data_size =
        @floc.fuget(hl)

      @fwgt.fuget(hl) if @fwgt

      case dfmt
      when "UR4"
	@data_type = "real"
      when "UR8"
	@data_type = "double"
      end

      @dend = 1 if !axis["name"].scan(/[A-Z]1$/)[0].nil?

      @short_name = @item
      @long_name = (title0.split + title1.split).to_s

      case styp
      when 1
	@positive = nil
	@log = nil
      when -1
	@positive = "down"
	@log = nil
      when 2
	@positive = nil
	@log = true
      when -2
	@positive = "down"
	@log = true
      end

      @get = false

    end

    def get

      @get = true

      @floc.fuget([[@data_type, @data_size.to_i]])[(@dend==1 ? 0 : true)]
    end

    def get_wgt

      @fwgt && @fwgt.fuget([[@data_type, @data_size.to_i]])[(@dend==1 ? 0 : true)]

    end

    def get?

      return @get

    end

    def exist_wgt?

      return !@fwgt.nil?

    end

  end

  attr_accessor :data, :short_name, :long_name, :unit,
    :dmin, :dmax, :divs, :divl, :styp,
    :format_id, :dataset, :title, :edit,
    :cdate, :csign, :mdate, :msign,
    :dims

  def initialize(infile, outfile, *opts)

    @infile = File::open(infile, "rb")
    @outfile = NetCDF.create(outfile)

    @gt3_header_path = "."

    if opts[0].class == Hash then

      @gt3_header_path = opts[0][:axis] if opts[0].key? :axis
      if (opts[0].key? :endian) then
	@infile.set_endian(opts[0][:endian])
      end

    end

    @hl = nil
    @last_axis = nil
    @dims = Hash.new

    @wrote_global_attr = false

    @time = Array.new
    @last_time = nil

  end

  def read_data_header

    if @hl.nil?
      @hl = Array.new
      @edit_short = Array.new(8)
      @edit_long = Array.new(8)
      @title = Array.new(2)
      @opt = Array.new(3)
      @memo = Array.new(12)

      64.times do
	@hl.push ["character", 16]
      end
    end

    @axis = Array.new(3)
    (0...3).each {|i| @axis[i] = {}}

    @idfm, @dset, @item,
      @edit_short[0], @edit_short[1], @edit_short[2], @edit_short[3],
      @edit_short[4], @edit_short[5], @edit_short[6], @edit_short[7],
      @fnum, @dnum, @title[0], @title[1], @unit,
      @edit_long[0], @edit_long[1], @edit_long[2], @edit_long[3],
      @edit_long[4], @edit_long[5], @edit_long[6], @edit_long[7],
      time, @date, @utime, @tdur,
      @axis[0]["name"], @axis[0]["min"], @axis[0]["max"],
      @axis[1]["name"], @axis[1]["min"], @axis[1]["max"],
      @axis[2]["name"], @axis[2]["min"], @axis[2]["max"],
      dfmt, @miss, @min, @max, @divs, @divl, @styp,
      @opt[0], @opt[1], @opt[2], @memo[0], @memo[1], @memo[2], @memo[3],
      @memo[4], @memo[5], @memo[6], @memo[7], @memo[8], @memo[9], @memo[10],
      @memo[11], @cdate, @csign, @mdate, @msign, @data_size =
      @infile.fuget(@hl)

    @time.push time.to_i if !(@time.include? time.to_i)

    return nil if @idfm.nil? # EOF

    @item.gsub!(/ /,'_')
    @item.gsub!(/,/,'_')

    case dfmt
    when "UR4"
      @data_type = "real"
    when "UR8"
      @data_type = "double"
    end

    @get = false

    return true
             
  end

  def check_axis(*opts)

    if !(@last_axis == @axis) then

      @current_dims = Array.new

      @axis.each {|ax|

	if (ax["name"] != "") && (ax["name"] != @item) then

	  @current_dims.push ax

	  # まだ開いていない軸ファイルを開く

	  if !(@dims.key? ax["name"]) then

	    @dims[ax["name"]] = Axis.new(ax, @gt3_header_path, *opts)

 	  end

	end

      }

    end

    @last_axis = @axis.clone

  end

  def get

    if @get == false then
      @get = true
      @dat = @infile.fuget([[@data_type, @data_size]])
    else
      @dat
    end

  end

  def get?

    return @get

  end

  def write_header

    @outfile.redef

#
# global attributes
#

    if !@wrote_global_attrs then

      fattrs = @outfile.att_names
      attrname = ["dataset_name", "gt3_file_number", "history", "gt3_edit"]

      history = "created #{cdate} by #{csign}\n"
      history += "last modified #{mdate} by #{msign}"

      gt3_edit = ""
      @edit_short.each_with_index {|e,i|
	gt3_edit += sprintf("[%s] : %s\n", e, @edit_long[i]) if e != ""
      }

      [@dset, @fnum, history, gt3_edit].each_with_index {|e,i|
	if !(fattrs.include? e) then
	  @outfile.put_att(attrname[i],e) if e != ""
	end
      }

      @wrote_global_attrs = true

    end

#
# dimensions/variables
#

    fdims = @outfile.dim_names

    if !(fdims.include? "time")    
      d = @outfile.def_dim("time", 0)
      v = @outfile.def_var("time", "float", [d])
    end

    @current_dims.each {|dim|
      cdim = @dims[dim["name"]]
      if !(fdims.include? cdim.long_name[0..2]) then
	d = @outfile.def_dim(cdim.long_name[0..2],
			     dim["max"].to_i-dim["min"].to_i+1)
      type = {"double"=>"float","real"=>"sfloat"}[cdim.data_type]
	v = @outfile.def_var(cdim.long_name[0..2], type, [d])
	v.put_att("units", cdim.unit)
	v.put_att("long_name", cdim.long_name)
	v.put_att("divs", cdim.divs.to_f)
	v.put_att("divl", cdim.divl.to_f)
	v.put_att("positive", cdim.positive) if !cdim.positive.nil?
	v.put_att("log", "yes") if !cdim.log.nil?
        if cdim.exist_wgt?
	  v = @outfile.def_var(cdim.long_name[0..2]+"_wgt", type, [d])
	  v.put_att("longname", cdim.long_name + "_wgt")
        end
      end
    }

    if !(@outfile.var_names.include? @item) then
      d = Array.new
      @current_dims.each{|dim|
	d.push @outfile.dim(@dims[dim["name"]].long_name[0..2])
      }
      d.push @outfile.dim("time")
      type = {"double"=>"float","real"=>"sfloat"}[@data_type]
      v = @outfile.def_var(@item, type, d)
      v.put_att("units", @unit)
      v.put_att("long_name", (@title[0].split + @title[1].split).to_s)
      v.put_att("missing_value", @miss.to_f)
      v.put_att("divs", @divs.to_f)
      v.put_att("divl", @divl.to_f)
      v.put_att("gt3_min", @min)
      v.put_att("gt3_max", @max)
    end

    @outfile.enddef

  end

  def write_data

    @outfile.enddef

    @current_dims.each{|dim|
      if !((cdim = @dims[dim["name"]]).get?) then
	loc = cdim.get
        wgt = cdim.get_wgt
        if Array===loc || dim["min"].to_i != 1 || dim["max"].to_i != 1
          loc = loc[(dim["min"].to_i-1)..(dim["max"].to_i-1)]
          wgt = wgt[(dim["min"].to_i-1)..(dim["max"].to_i-1)] if wgt
        end
	v_loc = @outfile.var(cdim.long_name[0..2])
	v_loc.put(loc)
	v_wgt = @outfile.var(cdim.long_name[0..2]+"_wgt") if wgt
        v_wgt.put(wgt) if wgt
      end
    }

    if @last_time != @time then
      @outfile.var("time").put(@time[-1], "index"=>[@time.size-1])
    end

    rstr = Array.new
    rend = Array.new

    @current_dims.each {|ax|
      rstr.push ax["min"].to_i - 1
      rend.push ax["max"].to_i - 1
    }

    rstr.push @time.size - 1
    rend.push @time.size - 1

    print "TIME = " + @time[-1].to_s + "\n"
    @outfile.var(@item).put(self.get, "start"=>rstr, "end"=>rend)

    @outfile.sync

    @last_time = @time.clone

  end

end

if $0 == __FILE__ then

  infile = ARGV.shift

  if infile.nil? then
    raise "Usage : #$0 infile [outfile] [axis=path_to_axis] [endian=big_or_little]"
  end

  if !ARGV[0].nil? && !ARGV[0].include?("=") then
    outfile = ARGV.shift
  else
    outfile = infile + ".nc"
  end

  axis = "."
  endian = :big
  opts = ARGV
  opts.each{|opt|
    case opt
    when /^endian=/
      case opt[7..-1]
      when "big"
	endian = :big
      when "little"
	endian = :little
      else
	raise("option of endian must be big or little, #{opt[6..-1]}")
      end	
    when /axis=/
      axis = opt[5..-1]
    end
  }

  a = Gt3nc.new(infile, outfile, :axis=>axis, :endian=>endian)

  while(a.read_data_header)

    a.check_axis(:endian=>endian)

    a.write_header

    a.write_data

  end

end
