require "fileutils"
require "vizshot_gfdnavi"
require "file_gfdnavi"
require "open-uri"

module ExecuteAnalysis

  WORK_FILE_BASENAME = "temp"

  private

  def diagrams_dir
    if "ActionController::TestSession" == session.class.to_s   # for test
      dir = "#{GFDNAVI_DIAGRAM_PATH}/test"
      FileUtils.makedirs(dir) unless File.exist?(dir)
      return dir
    end
    dir = session[:diagrams_dir]
    unless dir
      dir = File.join(GFDNAVI_DIAGRAM_PATH, Digest::MD5.hexdigest(session.session_id)[0,16])
      FileUtils.makedirs(dir) unless File.exist?(dir)
      session[:diagrams_dir] = dir
    end
    return dir
  end

  def work_dir
    if "ActionController::TestSession" == session.class.to_s   # for test
      dir = "#{GFDNAVI_DIAGRAM_PATH}/test"
      FileUtils.makedirs(dir) unless File.exist?(dir)
      return dir
    end
    dir = session[:work_dir]
    unless dir
      dir = File.join(GFDNAVI_WORK_PATH, Digest::MD5.hexdigest(session.session_id)[0,16])
      FileUtils.makedirs(dir) unless File.exist?(dir)
      session[:work_dir] = dir
    end
    return dir
  end

  def debug(obj)
    obj = obj.inspect unless String === obj
    if GFDNAVI_DEBUG
      STDOUT.print obj,"\n"
      STDERR.print obj,"\n"
    end
  end

  def system_with_error(command, prio)
    debug( command )
    if /linux/ =~ Config::CONFIG["arch"]
      rr,rw = IO.pipe
      er,ew = IO.pipe
      pid = fork {
        rr.close
        er.close
        STDERR.reopen(ew)
        begin
          Process.setpriority(Process::PRIO_PROCESS, 0, prio)
        rescue Errno::EACCES
        end
        res = system(command)
        rw.print res
        rw.close
        ew.close
      }
      rw.close
      ew.close
      Process.waitpid(pid)
      res = rr.gets == "true" ? true : false
      mes = er.readlines
      debug( mes.join("") ) unless res
      mes = mes.delete_if{|me| /^\s*from / =~ me }.collect{|me|
        me.sub(/[\/\w\.]*\.rb:\d*:in /,"")
      }
      mes = mes.join("").gsub(/\n/,"<br/>")
      rr.close
      er.close
      if res
        mes = nil
      end
    else
      res = system( command )
      mes = res ? nil : "failed to execute"
    end
    ActiveRecord::Base.connection.reconnect!
    return [res, mes]
  end

  def get_ofname
    files = Dir["#{work_dir}/#{WORK_FILE_BASENAME}_*.nc"].sort
    if files.length >0
      files[-1] =~ /.*_(\d*).nc$/
      num = $1.next
    else
      num = "000"
    end
    return "#{work_dir}/#{WORK_FILE_BASENAME}_#{num}.nc"
  end



  def variables_set(params_vars)
    analysis = session[:analysis] || Analysis.new
    analysis.user = session[:user]
    analysis.variable_clear
    params_vars && params_vars.each do |k,v|
      var = Variable.find(:first, :conditions => ["path=?", k], :user => session[:user] )

      if var
        session[:variables_list] ||= Array.new
        session[:variables_list].push(var) unless session[:variables_list].include?(var)
      elsif /^http:\/\/([^\/]*)(.*)$/ =~ k
        ary = k.split("/")
        path = ary[0..-2].join("/")
        name = ary[-1]
        begin
          io = open(path+".html")
          io.each_line{|line|
            if /OPeNDAP/ =~ line
              opendap = true
              break
            end
          }
          opendap = false
        rescue OpenURI::HTTPError
          opendap = false
        end
        if opendap
        else
          ofname = get_ofname
          File.open(ofname,"wb"){|file|
            io = open(path)
            while (str = io.read(1024))
              file.write str
            end
          }
          path = ofname.sub(/^#{GFDNAVI_WORK_PATH}/,"")
        end
        var = Variable.new(:path => "temporary:#{path}/#{vname}", :name => vname, :file => path )
        session[:temp_variables_list] ||= Array.new
        session[:temp_variables_list].push var
      elsif /temp_(\d*)/ =~ k
        var = session[:temp_variables_list][$1.to_i]
      end
      if var && v!="0"
        analysis.variables.push var unless analysis.variables.include?(var)
      else
        analysis.variables.delete var
      end
    end
    if ( var = analysis.variables[0] )
      ans = [var]
      pa = var
      while (pa = pa.parent)
        ans.unshift pa
      end
      ans.each{|v|
        v.draw_parameters.each{|dp|
          analysis[dp.name] = dp.value
        }
      }
    end
    return analysis
  end


  def clear_diagram_files
    if ( dir = diagrams_dir )
      files = Dir[dir+"/gfdnavi_*.png"].each do |file|
        File.delete(file)
      end
    end
    session[:diagrams] = nil
  end


  def get_diagram(analysis, keep=nil)
    res = analysis.get_vizshots(WORK_FILE_BASENAME)
    unless res[0]
      return false, res[1]
    else
      keep = analysis.draw_keep if keep.nil?
      vizs_to_diagram(res[1], keep, analysis.draw_share)
    end
  end

  def vizs_to_diagram(vizs, keep, share)
    diagrams = Array.new
    draw_or_cache = [0,0]
    NumRu::VizShot.dumpdir = work_dir
    vizs.length.times{|i|
      viz = vizs[i]
      msss = nil
      diagram_cache = nil
      if ( diagram_cache = find_diagram_cache(viz) )
        draw_or_cache[1] += 1
        flag = true
        diagram_fname = diagram_cache
      else
        viz.dump_code( nil, true, :image_dump => true )
        wdir = work_dir
        wdir.gsub!(/\//,File::ALT_SEPARATOR) if File::ALT_SEPARATOR
        command = "cd #{wdir} && ruby \"#{work_dir}/#{WORK_FILE_BASENAME}.rb\""
        flag, mess = system_with_error(command, 10)
        diagram_fname = "#{work_dir}/#{WORK_FILE_BASENAME}_001.png"
        push_diagram_cache(diagram_fname, viz, share) if flag
        draw_or_cache[0] += 1
      end
      if flag
        keep2 = i==0 ? keep : true
        dir = diagrams_dir
        return nil unless dir
        clear_diagram_files unless keep2
        dest, page = dest_fname(dir,"png")
        File.copy( diagram_fname, dest )
        diagram = {:fname => File.basename(dest), :id => page}
      end
      unless diagram
        messages = "could not draw diagram<br/>"
	if mess
	  messages += mess
	else
	  messages += "[BUG] cannot move diagram file"
	end
        return false, messages
      end
      diagram[:vizshot] = viz
      diagrams.push diagram
    }
    return true, diagrams, keep, draw_or_cache
  end

  def diagram_to_dginfo(diagram)
    fname = diagram[:fname]
    diagram_id = diagram[:id]
    path = diagrams_dir.sub(/^#{GFDNAVI_PUBLIC_PATH}/,"") + "/#{fname}"
    id = "diagram_#{diagram_id}"
    return [id, path, diagram_id, diagram[:saved], diagram[:vizshot]]
  end

  def find_diagram_cache(viz)
    return nil if GFDNAVI_DISABLE_DIAGRAM_CACHE
    uri_params = Analysis.uri_params_from_vizshot(viz)
    viz.get_variables.each{|v|
      unless v.id
        return nil
      end
      v.diagram_caches(true).each{|dc|
        if dc.uri_params == uri_params
          if (dcs = dc.diagram_cache_sessions.find_by_session(session.session_id))
            if session[:analysis] && session[:analysis].draw_share && !dcs.share
              dcs.share = true
              dcs.save!
            end
          elsif !( /^aws/ =~ session.session_id )
            dcs = DiagramCacheSession.new
            dcs.diagram_cache = dc
            dcs.session = session.session_id
            dcs.save!
          end
          return dc.path
        end
      }
    }
    return nil
  end

  def push_diagram_cache(fname_from, viz, share)
    return nil if GFDNAVI_DISABLE_DIAGRAM_CACHE
    uri_params = Analysis.uri_params_from_vizshot(viz)
    return nil if uri_params.nil?
    dc = DiagramCache.new
    dc.uri_params = uri_params
    fname_dest = File.temp_name(GFDNAVI_DIAGRAM_CACHE_PATH, ".png")
    File.copy(fname_from, fname_dest)
    dc.path = fname_dest
    analysis = Analysis.from_vizshot(viz)
    analysis.user = session[:user]
    dc.label = analysis.diagram_label
    dc.save!
    viz.get_variables.each{|v|
      if Variable===v && ! v.id
        return nil
      end
      v = Variable===v ? v : Variable.find(v)
      dcd = DiagramCacheDatum.new
      dcd.diagram_cache = dc
      dcd.variable = v
      dcd.save!
      dcs = DiagramCacheSession.new
      dcs.diagram_cache = dc
      dcs.session = session.session_id
      dcs.share = share
      dcs.save!
    }
  end

  def viz_to_script_and_data(viz, work_dir)
    dir = work_dir
    NumRu::VizShot.dumpdir = dir
    paths = viz.dump_code_and_data("gfdnavi")
    names = paths.collect{|path| File.basename(path)}
    command = "cd #{dir} && ruby -r #{File.join(Dir.pwd,"lib/tar")} -e 'Tar.zcf(\"#{WORK_FILE_BASENAME}.tar.gz\", #{names.inspect})'"
    begin
      res = system_with_error(command, 19)
      if res[0]
        return true, "#{work_dir}/#{WORK_FILE_BASENAME}.tar.gz"
      else
        return false, res[1]
      end
    ensure
      File.delete(*paths)
    end
  end



  def create_new_variable(analysis, work_dir)
    vlen = analysis.variables.length
    function = analysis.function
    function_variables_order = analysis.function_variables_order
    function_arguments = analysis.function_arguments
    func_nvars = function.nvars
    unless function && (vlen==func_nvars || function_variables_order)
      return false, "invalid operation was required"
    else
      messages = ""
      new_vars = Array.new
      variables = analysis.variables
      (vlen/func_nvars).times{|n|
        if func_nvars == 1
          vars = [ variables[n] ]
        else
          vars = function_variables_order[n].collect{|i| variables[i] }
        end
        vname = vars.collect{|v| v.name }.join("_")
        outputs = function.function_outputs
        ovnames = Array.new
        attrs = Array.new
        outputs.length.times{|i|
          output = outputs[i]
          ovnames[i] = "#{vname}_#{output.subscript}"
          attrs[i] = [["analysis", output.description + " calculated from " + vname]]
        }
        fa = function.function_arguments
        args_length = fa.length
        if args_length != 0
          func_args = Array.new
          args_length.times{|i|
            func_args[i] = function_arguments[i] || fa[i].default
            func_args[i] = func_args[i].inspect
	  }
        else
          func_args = nil
        end
        script = function.script
        owner = function.owner
        nvars = vars.length
        names = vars.collect{|var| [var.fname, var.vname]}
        code = <<"EOF"
require "numru/gphys"
begin
 require File.dirname(__FILE__)+"/../../../lib/gphys_gfdnavi"
rescue LoadError
end
begin
 require File.dirname(__FILE__)+"/../../../../lib/gphys_gfdnavi"
rescue LoadError
end
include NumRu

GPhys::read_size_limit_2 = #{GPHYS_READ_SIZE_LIMIT_2.inspect}
GPhys::read_size_limit_1 = #{GPHYS_READ_SIZE_LIMIT_1.inspect}

gphys = Array.new
EOF
        nvars.times do |i|
          code +=<<"EOF"
if (arg = ARGV.shift)
  arg = arg.split(",")
  fname#{i} = arg.length==1 ? arg.first : arg
else
  fname#{i} = "gfdnavi#{i}.nc"
end
vname#{i} = "#{names[i][1]}"
gphys[#{i}] = GPhys::IO.open(fname#{i}, vname#{i})
gphys[#{i}] = gphys[#{i}].cut(#{analysis.index.inspect})
EOF
        end
        code +=<<"EOF"
ovnames = Array.new
EOF
        ovnames.length.times do |i|
          code +=<<"EOF"
ovnames[#{i}] = "#{ovnames[i]}"
EOF
        end
        if script
          code +=<<"EOF"
args = Array.new
EOF
          arg = Array.new
          if Array===func_args
            func_args.length.times do |i|
              str = func_args[i]
              if /^(\[.*), ""\]$/ =~ str
                str = $1+"]"
              end
              code +=<<"EOF"
args[#{i}] = #{str}
EOF
              arg.push "arg#{i}"
            end
          end
          nvars.times{|i| arg.push "gphys#{i}"}
          arg = arg.join(", ")
          code +=<<"EOF"
proc = Proc.new{|#{arg}|
EOF
          unless owner.super_user?
            code +=<<"EOF"
  $SAFE = 3
EOF
          end
          code +=<<"EOF"
  #{script}
}
gphys = proc.call(*(args+gphys))
if !gphys.is_a?(Array)
  gphys = [gphys]
end
if !gphys[0].is_a?(GPhys)
  raise "Return value of the block must be a GPhys or an Array of GPhys"
end
EOF
        end
        code +=<<"EOF"
ofname = ARGV.shift || "output.nc"
file = NetCDF.create(ofname, true)
$SAFE=1
gphys.length.times do |i|
  gp = gphys[i]
  gp.name = ovnames[i]
  GPhys::IO.write( file, gp )
end
file.close
EOF
        args = names.collect{|f,v| Array===f ? f.join(",") : f}
        ofname = get_ofname
        args.push ofname
        args = args.join(" ")
        debug( code )
        script_name = "#{work_dir}/#{WORK_FILE_BASENAME}.rb"
        File.open(script_name,"w"){|file|
          file.print code
        }
        command = "ruby #{script_name} #{args}"
        res = system_with_error(command, 19)
        if res[0]
          nvars = ovnames.length
          nvars.times{|i|
            name = ovnames[i]
            var = Variable.new
            var.file = "temporary:#{ofname.sub(/^#{GFDNAVI_WORK_PATH}/,"")}"
            var.path = File.join(var.file, name)
            var.name = name
            vars.each{|v|
              var.references_tmp << v
            }
            if vars[0].spatial_and_time_attributes[0]
#              var.starttime = Date.new()
#              var.endtime = Date.new()
            end
            if attrs && attrs[i]
              attrs[i].each do |k,v|
                var.keyword_attributes.build({:name => k, :value => v})
              end
            end
            new_vars.push var
          }
        else
          messages += "faild to create some variable<br/>" + res[1]
        end
      }
      return new_vars, messages
    end
  end

  def dest_fname(path,suffix)
    page = "000"
    files = Dir[path+"/gfdnavi_*.#{suffix}"].sort
    if files.length >0
      files[-1] =~ /.*gfdnavi_(\d*)\.#{suffix}$/
      page = $1.next
    end
    fname = "#{path}/gfdnavi_#{page}.#{suffix}"
    dname = File.dirname( fname )
    File.makedirs( dname ) unless File.exists?( dname )
    return [fname, page.to_i]
  end


end
