require "soap/wsdlDriver"
require "pp"
Dependencies.require_or_load "webservice_analysis_api"

class WorkFlowController < ApplicationController
  layout "gfdnavi"

  def index
    wsdl, apis = parse_wsdl
    ags = "another gfdnavi site"
    @wsdls = [wsdl, ags]
    @apis = {wsdl => apis, ags => apis}
  end

  def add_wsdl
    wsdl, apis = parse_wsdl( params[:url] )
    render(:update){|page|
      @wsdls = [wsdl]
      @apis = {wsdl => apis}
      page << render(:partial => "push_wsdls")
    }
  end

  def execute
    work_flow = WorkFlow.new(params[:connector], params[:apis])
    result = work_flow.execute(local_ws_analysis, request)
    render(:update){|page|
      page << "alert('#{result.inspect}')"
    }
  end

  def get_ruby_code
    work_flow = WorkFlow.new(params[:connector], params[:apis])
    code = work_flow.dump_ruby_code
    send_data( code, :filename=>"gfdnavi_workflow.rb", :type=>"text/plain")
  end


  private
  def dump_ruby_code
  end

  def local_ws_analysis
    prot = request.ssl? ? "https" : "http"
    "#{prot}://#{request.host_with_port}#{request.relative_url_root}/webservice_analysis/service.wsdl"
  end

  def parse_wsdl(url=nil)
    wac = WebserviceAnalysisController.new
    class << wac
      attr_accessor :request
      def get_wsdl
        to_wsdl
      end
    end
    wac.request = request
    if url
      wsdl = WSDL::Importer.import(url)
    else
      location = local_ws_analysis
      opt = { :location => location, :originalroot => nil }
      wsdl = WSDL::Parser.new(opt).parse( wac.get_wsdl )
      url = location
    end
    service = wsdl.services[0]
    port = service.ports.find{|port| !port.soap_address.nil? }
    apis = Hash.new
    port.inputoperation_map.each{|op,opinfo|
      hash = Hash.new
      opinfo.bodyparts.each{|param|
        hash[param.name] = qname2stype( param.type, wsdl )
      }
      apis[op.name] = {:input => hash}
    }
    port.outputoperation_map.each{|op,opinfo|
      hash = Hash.new
      opinfo.bodyparts.each{|param|
        hash[param.name] = qname2stype( param.type, wsdl )
      }
      apis[op.name][:output] = hash
    }
    return [url, apis]
  end

  def qname2stype(qname, wsdl)
    @stypes ||= Hash.new
    return @stypes[qname] if @stypes[qname]
    @complextypes ||= Hash.new
    @complextypes[wsdl] ||= wsdl.collect_complextypes
    ctypes = @complextypes[wsdl]
    ctypes.each{|ctype|
      next unless ctype.name == qname
      case ctype.check_type
      when :TYPE_ARRAY
        @stypes[qname] = [qname2stype(ctype.child_type, wsdl)]
        return @stypes[qname]
      when :TYPE_STRUCT
        hash = Hash.new
        ctype.each_element{|elm| hash[elm.name.name] = qname2stype(elm.type, wsdl) }
        @stypes[qname] = hash
        return hash
      end
    }
    return qname.name
  end

end


class WorkFlow

  def initialize(params_connector, params_apis)
    set_connector(params_connector)
    set_entity(params_apis)
  end

  def execute(localhost, request=nil)
    result = Hash.new
    @apis.each{|api|
      uri_wsdl = api[:uri_wsdl]
      api_name = api[:api_name]
      params = api[:params]
      params.collect!{|ary|
        k,v = ary
        v = v.pretty_inspect.chomp
        v = v.gsub(/"(result[^"]*)"/, '\1') if String===v
        eval(v)
      }
      if uri_wsdl == localhost
        waa = WebserviceAnalysisApi.api_methods[api_name.downcase.to_sym]
        params = waa.cast_expects(params)
        was = WebserviceAnalysisService.new(request)
        res = was.send(api_name.downcase, *params)
      else
        driver = SOAP::WSDLDriverFactory.new(uri_wsdl).create_rpc_driver
        res = driver.send( api_name, *params )
      end
      result["#{api_name}_#{api[:api_id]}"] = res
    }
    return result
  end

  def dump_ruby_code
    code = Array.new
    code.push "require 'soap/wsdlDriver'\n"
    code.push "result = Hash.new"
    @apis.each{|api|
      code.push ""
      code.push "uri_wsdl = '#{api[:uri_wsdl]}'"
      api_name = api[:api_name]
      code.push "api_name = '#{api_name}'"
      params = api[:params]
      params.each{|ary|
        k,v = ary
        v = v.pretty_inspect.chomp
        v = v.gsub(/"(result[^"]*)"/, '\1') if String===v
        code.push "#{k} = #{v}"
      }
      code.push "driver = SOAP::WSDLDriverFactory.new(uri_wsdl).create_rpc_driver"
      code.push "result['#{api_name}_#{api[:api_id]}'] = driver.send( api_name, #{params.collect{|ary|ary[0]}.join(", ")} )"
    }
    return code.join("\n") + "\np result\n"
  end

  private
  def set_connector(params)
    @parents = Hash.new
    @connectors = Hash.new
    if params
      params.each{|n,param|
        from = param["from_api"]
        dest = param["dest_api"]
        @parents[dest] ||= Array.new
        @parents[dest].push from
        @connectors[from]  ||= Array.new
        @connectors[from].push param
      }
    end
  end

  def set_entity(params)
    @apis = Array.new
    api_ids = params.keys
    while ( api_ids.length > 0 )
      flag = true
      api_ids.dup.each{|i|
        if @parents[i].nil? || @parents[i].length == 0
          hash = Hash.new
          api_ids.delete(i)
          flag = false
          param = params[i]
          hash[:api_id] = i
          hash[:uri_wsdl] = param.delete("uri_wsdl")
          api_name = param.delete("api_name")
          hash[:api_name] = api_name
          hash[:params] = Array.new
          param.each{|ary|
            hash[:params].push [ary[0], modify_param(ary[1])]
          }
          @apis.push hash
          if @connectors[i]
            @connectors[i].each{|con|
              dest = con["dest_api"]
              @parents[dest].delete i
              from_param = con["from_param"]
              dest_param = con["dest_param"]
              dest_param = dest_param.sub(/^apis\[\d*\]/,"")
              dest_param = dest_param.sub(/^\[/,"").sub(/\]$/,"")
              dest_param = dest_param.split("][")
              param = params[dest]
              (dest_param.length-1).times{|n|
                param = param.send("[]", dest_param[n])
              }
              i = params.length - api_ids.length - 1
              param.send("[]=", dest_param[-1], "result['#{api_name}_#{i}']#{from_param}")
            }
          end
        end
      }
      if flag
        raise "there are entities which depend each other"
      end
    end
  end

  def modify_param(params)
    if Hash === params
      hash = Hash.new
      params.each{|k,v|
        v = modify_param(v) if Hash===v
        v = nil if v=="" || v=={}
        hash[k] = v
      }
      keys = hash.keys
      if keys.length>0 && keys.collect{|c| c.to_i.to_s} == keys
        ary = Array.new
        hash.each{|i,v| ary[i.to_i] = v unless v.nil? }
        hash = ary
      end
      return hash == {} ? nil : hash
    elsif Array === params
      return params.collect{|v| modify_param(v) }
    else
      return params
    end
  end



end
