require "numru/ggraph"
require "yaml"

module NumRu
  class VizShot

    @@ms_windows = false
    def self.windows
      @@ms_windows = true
    end

    @@dumpdir = './'
    def self.dumpdir=(dirname)
      @@dumpdir = dirname.sub(/([^\/])$/,'\1/')   # to end with '/'
    end

    @@basename_default = 'vizshot_dump'   ## DCL::swcget('FNAME').strip 
    def self.basename_default=(basename)
      @@basename_default = basename
    end

    ONED_METHODS = [:plot_1d, :line, :mark]
    TWOD_METHODS = [:tone_cont, :vector, :tone, :contour]

    def initialize(opt = nil)
      opt = opt ? opt.dup : Hash.new
      @set = Hash.new
      @set[:admin] = Hash.new
      @set[:admin][:iwidth]  = ( opt.delete(:iwidth)  || 700 )
      @set[:admin][:iheight] = ( opt.delete(:iheight) || 700 )
      @set[:admin][:xdiv]    = ( opt.delete(:xdiv) || 1 )
      @set[:admin][:ydiv]    = ( opt.delete(:ydiv) || 1 )
      @set[:admin][:basename] = ( opt.delete(:basename) || @@basename_default)

      if opt.length > 0
	raise ArgumentError,"Unsupported option(s) #{opt.keys.inspect}" 
      end

      @plots = Array.new
    end

    def plot(arg)
      arg[:file] = File.expand_path(arg[:file]) if arg[:file] && arg[:file].is_a?(String)
      arg[:file2] = File.expand_path(arg[:file2]) if arg[:file2] && arg[:file2].is_a?(String)
      @plots.push(arg) 
      @plots.length - 1      # index of the current plot
    end

    def add(vizshot)
      viz_new = self.semi_deep_clone
      vizshot.plots.each{|plot| viz_new.plot(plot)}
      viz_new
    end

    def replace_plot!(index, arg)
      @plots[index].update(arg)
    end

    def set_fig(opt)
      @set[:fig] = opt
    end

    def set_map(opt)
      @set[:map] = opt
    end

    def set_axes(opt)
      @set[:axes] = opt
    end

    def set_tone(opt)
      @set[:tone] = opt
    end

    def set_contour(opt)
      @set[:contour] = opt
    end

    def execute(opt=nil)
      if opt  # must be a Hash
	opt = opt.dup
	postscript = opt.delete(:postscript)
	image_dump = opt.delete(:image_dump)
	if opt.length > 0
	  raise ArgumentError,"Unsupported option(s) #{opt.keys.inspect}" 
	end
      end

      setup(postscript,image_dump)
      first = true
      @plots.each do |p| 
        exec_plot(p, first)
        first = false
      end
      finish
    end

    def dump_code(basename=nil, all_in_one=false, exec_opt=nil)
      @set[:admin][:basename] = basename if basename
      path = @@dumpdir + @set[:admin][:basename] + '.rb'
      File.open(path,'w'){|f| f.print(gen_code(all_in_one, exec_opt))}
      path
    end

    def dump_code_and_data(basename=nil, all_in_one=false, exec_opt=nil)
      @set[:admin][:basename] = basename if basename
      viz = self.dup
      data_paths = viz.cut_out_data(@set[:admin][:basename])
      code_path = viz.dump_code(basename, all_in_one, exec_opt)
      [code_path] + data_paths
    end

    def gen_code(all_in_one = false, exec_opt=nil)
      if all_in_one
        libsrc = File.open(__FILE__)
        code = ""
        while (line=libsrc.gets)
          break if /^#.*ENDOFLIB/ =~ line
          code << line
        end
        code << "####################\n"
      else
        code = 'require "numru/vizshot"'
      end
      code += "\nset = <<YML\n\n#{@set.to_yaml}\nYML\n\n"
      code += "\nplots = <<YML\n\n#{@plots.to_yaml}\nYML\n\n"
      ecode = <<-EOS
        plots = YAML.load(plots)
        set = YAML.load(set)
        viz = NumRu::VizShot.new(set[:admin])
        viz.set_fig(set[:fig]) if set[:fig]
        viz.set_map(set[:map]) if set[:map]
        viz.set_axes(set[:axes]) if set[:axes]
        viz.set_contour(set[:contour]) if set[:contour]
        viz.set_tone(set[:tone]) if set[:tone]
        plots.each{|p| viz.plot(p)}
        viz.execute(#{exec_opt.inspect})
      EOS
      ecode.gsub!(/^        /,'')
      code << ecode
    end

    ###################################################
    protected

    def semi_deep_clone
      viz_new = NumRu::VizShot.new(@set[:admin].dup)
      viz_new.set_fig(@set[:fig]) if @set[:fig]
      viz_new.set_map(@set[:map]) if @set[:map]
      viz_new.set_axes(@set[:axes]) if @set[:axes]
      viz_new.set_contour(@set[:contour]) if @set[:contour]
      viz_new.set_tone(@set[:tone]) if @set[:tone]
      @plots.each{|plot| viz_new.plot(plot.dup) }
      return viz_new
    end

    def plots
      @plots
    end

    # for dump_code_and_data
    def cut_out_data(basename)
      newplots = Array.new
      data_paths = Array.new
      @plots.each_with_index do |pl, i|
	gphys, gphys2 = get_gphyses(pl.dup)
	cut = pl[:cut]
	slice = pl[:slice]
	cut2 = pl[:cut2] || cut
	slice2 = pl[:slice2] || slice
	method = pl[:method]
	if !cut && !slice && \
	    ( ( ONED_METHODS.include?(method) && gphys.rank==1 ) || \
	      ( TWOD_METHODS.include?(method) && gphys.rank==2 ) )
	  # no slicing needed
	  newplots[i] = pl
	else
	  filename = basename + sprintf("_%03d",i) + '.nc'
	  path = @@dumpdir + filename
	  pl = pl.dup
	  pl[:file] = filename
	  if ONED_METHODS.include?(method)
	    gphys = gphys.first1D
	  else
	    gphys = gphys.first2D
	  end
	  data_paths.push(path)
	  file = NetCDF.create(path)
	  GPhys::IO.write(file,gphys)
	  file.close
	  newplots[i] = pl
	end
	if gphys2
	  if !cut2 && !slice2 && \
	    ( ( ONED_METHODS.include?(method) && gphys2.rank==1 ) || \
	      ( TWOD_METHODS.include?(method) && gphys2.rank==2 ) )
	    # no slicing needed
	    newplots[i] = pl
	  else
	    filename = basename + sprintf("_%03d_2",i) + '.nc'
	    path = @@dumpdir + filename
	    pl = pl.dup
	    pl[:file2] = filename
	    if ONED_METHODS.include?(method)
	      gphys2 = gphys2.first1D
	    else
	      gphys2 = gphys2.first2D
	    end
	    data_paths.push(path)
	    file = NetCDF.create(path)
	    GPhys::IO.write(file,gphys2)
	    file.close
	    newplots[i] = pl
	  end
	end
	[:cut, :slice, :cut2, :slice2].each{|k| pl.delete(k)}
      end
      @plots = newplots

      data_paths
    end

    ###################################################
    private

    def get_gphyses(arg)
      file = arg.delete(:file)
      var = arg.delete(:var)

      if file && var
        gphys = GPhys::IO.open(file,var)
        slice  = arg.delete(:slice)
        cut = arg.delete(:cut)
        gphys = gphys[*slice] if slice
        case cut
        when Hash
          gphys = gphys.cut(cut)
        when Array
          gphys = gphys.cut(*cut)
        end
      else
	raise ArgumentError, "Need to specify a variable by :file and :var"
      end

      file2 = arg.delete(:file2)
      var2 = arg.delete(:var2)
      file2 = file if !file2 && var2
      var2 = var if file2 && !var2 

      if file2 && var2
        gphys2 = GPhys::IO.open(file2,var2)
        slice2  = arg.delete(:slice2) || slice
        cut2  = arg.delete(:cut2) || cut
        gphys2 = gphys2[*slice2] if slice2
        case cut2
        when Hash
          gphys2 = gphys2.cut(cut2)
        when Array
          gphys2 = gphys2.cut(*cut2)
        end
      else
	gphys2 = nil
      end

      [gphys,gphys2]
    end

    def exec_plot(arg, first)
      arg = arg.dup
      arg[:newfrm] = first if arg[:newfrm]==nil

      method = arg.delete(:method)    # :tone_cont, :line, ..

      gphys, gphys2 = get_gphyses(arg)

      opt = arg

      case method
      when :tone_cont
        tone_cont(gphys, opt)
      when :vector
	raise ArgumentError, "2nd variable is not specified" if !gphys2
        vector(gphys, gphys2, opt)
      when :plot_1d
        plot_1d(gphys, opt)
      when :tone
        tone_cont(gphys, opt.update(:tone=>true,:contour=>false))
      when :contour
        tone_cont(gphys, opt.update(:tone=>false,:contour=>true))
      when :line
        plot_1d(gphys, opt.update(:line=>true,:mark=>false))
      when :mark
        plot_1d(gphys, opt.update(:line=>false,:mark=>true))
      else
        raise ArgumentError, "unsuppored draw method: "+method.to_s
      end
    end

    def setup(postscript=nil,image_dump=nil)
      DCL::swiset("iwidth",  @set[:admin][:iwidth])
      DCL::swiset("iheight", @set[:admin][:iheight])
      if postscript
	raise "DCL on MS Windows does not support PostScript file O" if @@ms_windows
        DCL::gropn(2)
      else
        if image_dump
	  if !@@ms_windows
	    DCL::swcset("fname", @@dumpdir + @set[:admin][:basename])
	    DCL::swlset("lwnd", false)
	    DCL::gropn(4)
	    print "DUMP IMAGE FILE\n"
	  else
	    DCL::swlset("ldump", true)
	    DCL::gropn(1)
	  end
	else
	  DCL::gropn(1)
        end
      end
      DCL::sglset("lfull", true)
      DCL::sgpset("isub", 96)
      DCL::gllset("lmiss", true)

      if ( @set[:admin][:xdiv] > 1 || @set[:admin][:ydiv] > 1)
	DCL::sldiv('y',@set[:admin][:xdiv],@set[:admin][:ydiv])
      end

      GGraph::set_fig(@set[:fig]) if @set[:fig]
      GGraph::set_map(@set[:map]) if @set[:map]
      GGraph::set_axes(@set[:axes]) if @set[:axes]
      GGraph::set_contour(@set[:contour]) if @set[:contour]
      GGraph::set_tone(@set[:tone]) if @set[:tone]
    end
 
    def finish
      DCL::grcls
    end

    def tone_cont(gphys, opt)
      newfrm = opt.delete(:newfrm)
      contour = opt.delete(:contour) 
      tone = opt.delete(:tone) 
      color_bar = opt.delete(:color_bar)
      color_bar_options = opt.delete(:color_bar_options)
      if color_bar_options && !color_bar_options.is_a?(Hash)
	raise ":color_bar_options must be a Hash"
      end

      if @set[:fig] && (itr=@set[:fig]['itr']) && itr>=10
	lon = gphys.coord(0)
	lat = gphys.coord(1)
	if lon.min >= 120 && lon.max <= 150 && lat.min >= 20 && lat.max <=50
	  GGraph::set_map({'coast_japan'=>true})
	else
	  GGraph::set_map({'coast_world'=>true})
	end
	if itr==31
	  DCL.sgpset('lclip',true) 
	  GGraph.set_map('vpt_boundary'=>3)
	end
      end

      tone = contour = true if !tone && !contour   # should be what is meant

      if tone 
        GGraph::tone(gphys, newfrm, opt)
        newfrm = false
      end
      if contour
        GGraph::contour(gphys, newfrm, opt)
        newfrm = false
      end
      if tone && color_bar
	cbopt = color_bar_options || Hash.new
	cbopt['log'] = true if opt['log']
	GGraph::color_bar(cbopt)
      end
      newfrm
    end

    def vector(gpx, gpy, opt)
      newfrm = opt.delete(:newfrm)
      GGraph::set_unit_vect_options('vyuoff'=>-0.1,'vxuoff'=>0.07)
      GGraph::vector(gpx, gpy, newfrm, opt)
    end

    def plot_1d(gphys, opt)
      newfrm = opt.delete(:newfrm)
      contour = opt.delete(:contour) 
      line = opt.delete(:line) 
      mark = opt.delete(:mark)

      line = mark = true if !line && !mark   # should be what is meant

      if line 
        GGraph::line(gphys, newfrm, opt)
        newfrm = false
      end
      if mark
        GGraph::mark(gphys, newfrm, opt)
        newfrm = false
      end
      newfrm
    end


  end
end

########## ENDOFLIB ##########

if __FILE__ == $0

  if ARGV.length == 0
    raise <<-EOS

      USAGE: % ruby #{__FILE__} menu [dumpdir]
      where menu=0,1,2,..

    EOS
  end

  test_menu = ARGV[0].to_i
  NumRu::VizShot.dumpdir = ARGV[1] if ARGV[1]

  case test_menu

  when 0
    viz = NumRu::VizShot.new(:iwidth=>700,:iheight=>500)
    viz.set_fig('viewport'=>[0.15,0.85,0.2,0.55])
    viz.set_tone('tonc'=>true)
    viz.plot( :method => :tone_cont,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'level'=>400},
	      :color_bar=>true, "title"=>"T and [U,V]" )
    viz.plot( :method => :vector,
	      :file  => "../../testdata/UV.jan.nc",
	      :var=> "U",  :var2=> "V" , :cut => {'level'=>400},
	      "unit_vect" => true )  
	      # You can give GGraph options using string keys.

    paths = viz.dump_code_and_data(nil, true)
    print "\n!!! Code and data stored in #{paths.inspect} !!!\n\n"

    viz.execute

  when 1

    viz = NumRu::VizShot.new(:iwidth=>700,:iheight=>500)
    viz.set_fig('itr'=>10, 'viewport'=>[0.15,0.85,0.2,0.55])
    viz.plot( :method => :tone_cont,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'lon'=>90..270,'lat'=>0..90,'level'=>600},
	      :color_bar=>true)

    path = viz.dump_code(nil, true, :image_dump => true)
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute(:image_dump => true)

  when 2

    viz = NumRu::VizShot.new
    viz.set_fig('itr'=>31, 'viewport'=>[0.15,0.85,0.15,0.85])
    viz.plot( :method => :tone_cont,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'lat'=>10..90,'level'=>600},
	      :color_bar=>true)

    path = viz.dump_code
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute

  when 3

    viz = NumRu::VizShot.new
    viz.plot( :method => :plot_1d,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'lon'=>135}, "index"=>2,
	      :line => true, :mark => true )

    path = viz.dump_code
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute

  when 4

    # you can use :method=>:line instead of :method=>:plot_1d && :line=>true

    viz = NumRu::VizShot.new
    viz.plot( :method => :line,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'lon'=>135}, "index"=>2, "type"=>2 )

    path = viz.dump_code
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute

  when 5

    viz = NumRu::VizShot.new(:iwidth=>700,:iheight=>500,:xdiv=>2,:ydiv=>2)
    viz.set_fig('viewport'=>[0.15,0.85,0.2,0.55])
    [1000,600,400,200].each do |lev|
      viz.plot( :method => :tone_cont, :newfrm=>true,
	        :file => "../../testdata/T.jan.nc", 
	        :var=> "T", :cut => {'level'=>lev},
	        :color_bar=>true)
    end

    path = viz.dump_code
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute

  when 6

    viz = NumRu::VizShot.new(:iwidth=>700,:iheight=>500,:xdiv=>2,:ydiv=>2)
    viz.set_fig('viewport'=>[0.2,0.8,0.27,0.6])
    viz.plot( :method => :tone_cont, :newfrm=>true,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'level'=>1000},
	     :color_bar=>true, :color_bar_options=>{'vlength'=>0.25})
    viz.plot( :method => :tone_cont, :newfrm=>true,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'level'=>1000},
	      :color_bar=>true, :color_bar_options=>{'labelintv'=>1})
    viz.plot( :method => :tone_cont, :newfrm=>true,
	      :file => "../../testdata/T.jan.nc", 
	      :var=> "T", :cut => {'level'=>1000},
	      :color_bar=>true, 
              :color_bar_options=>{'landscape'=>true, 'labelintv'=>1,
               'vlength'=>0.6})

    path = viz.dump_code
    print "\n!!! Code written in #{path} !!!\n\n"

    viz.execute

  end

end


syntax highlighted by Code2HTML, v. 0.9.1