# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.

module Deltacloud::EC2

  class ActionHandler

    MAPPINGS = {
      :describe_availability_zones => { :method => :realms, :params => { 'ZoneName.1' => :id } },
      :describe_images => { :method => :images, :params => { 'ImageId.1' => :id }},
      :describe_instances => { :method => :instances, :params => {'InstanceId.1' => :id } },
      :describe_key_pairs => { :method => :keys, :params => {} },
      :create_key_pair => { :method => :create_key, :params => { 'KeyName' => :key_name }},
      :delete_key_pair => { :method => :destroy_key, :params => { 'KeyName' => :id }},
      :run_instances => { :method => :create_instance, :params => { 'ImageId' => :image_id, 'InstanceType' => :hwp_id, 'Placement.AvailabilityZone' => :realm_id, 'UserData' => :user_data }},
      :stop_instances => { :method => :stop_instance, :params => { 'InstanceId.1' => :id }},
      :start_instances => { :method => :start_instance, :params => { 'InstanceId.1' => :id }},
      :reboot_instances => { :method => :reboot_instance, :params => { 'InstanceId.1' => :id }},
      :terminate_instances => { :method => :destroy_instance, :params => { 'InstanceId.1' => :id }},
    }

    def self.mappings
      MAPPINGS
    end

    attr_reader :action

    def initialize(action)
      @action = action
    end

    def deltacloud_method
      self.class.mappings[action.action][:method]
    end

    def deltacloud_method_params
      parameters = action.parameters.dup
      self.class.mappings[action.action][:params].inject({}) do |result, p|
        result[p.last] = parameters.delete(p.first)
        result.delete_if { |k,v| v.nil? }
      end
    end

    def perform!(credentials, driver)
      @result = case deltacloud_method
        when :create_instance then driver.send(deltacloud_method, credentials, deltacloud_method_params.delete(:image_id), deltacloud_method_params)
        when :stop_instance then instance_action(driver, deltacloud_method, credentials, deltacloud_method_params.delete(:id))
        when :start_instance then instance_action(driver, deltacloud_method, credentials, deltacloud_method_params.delete(:id))
        when :destroy_instance then driver.send(deltacloud_method, credentials, deltacloud_method_params.delete(:id))
        when :reboot_instance then driver.send(deltacloud_method, credentials, deltacloud_method_params.delete(:id))
        else driver.send(deltacloud_method, credentials, deltacloud_method_params)
      end
    end

    # Some drivers, like RHEV-M does not return the instance object
    # but just notify client that the action was executed successfully.
    #
    # If we not received an Instance object, then we need to do additional
    # query.
    #
    def instance_action(driver, action, credentials, id)
      instance = driver.send(action, credentials, id)
      if instance.kind_of? Instance
        instance
      else
        driver.instance(credentials, :id => id)
      end
    end

    def to_xml(context)
      ResultParser.parse(action, @result, context)
    end

  end

  class ResultParser

    include ResultHelper

    attr_reader :query
    attr_reader :object
    attr_reader :context

    def self.parse(query, result, context)
      parser = new(query, result, context)
      layout = "%#{query.action.to_s.camelize}Response{:xmlns => 'http://ec2.amazonaws.com/doc/2012-04-01/'}\n"+
        "\t%requestId #{query.request_id}\n" +
        "\t=render(:#{query.action}, object)\n"
      Haml::Engine.new(layout, :filename => 'layout').render(parser)
    end

    def initialize(query, object, context)
      @context = context
      @query = query
      @object = object
    end

    def build_xml
      Converter.convert(query.action, object)
    end

    def render(template, obj)
      template_filename = File.join(File.dirname(__FILE__), 'views', '%s.haml' % template.to_s)
      Haml::Engine.new(File.read(template_filename), :filename => template_filename).render(self, :object => obj)
    end

  end

  class QueryParser

    class InvalidAction < StandardError; end

    def self.parse(params, request_id)
      parser = new(request_id, params)
      unless parser.valid_action?
        raise InvalidAction.new('Invalid action (%s)' % parser.action)
      else
        ActionHandler.new(parser)
      end
    end

    attr_reader :action
    attr_reader :parameters
    attr_reader :version
    attr_reader :expiration
    attr_reader :authentication
    attr_reader :request_id

    def initialize(request_id, params={})
      @request_id = request_id
      @action = (params.delete('Action') || 'Unknown').underscore.intern
      @version = params.delete('Version')
      @authentication = {
        :security_token => params.delete('SecurityToken'),
        :access_key_id => params.delete('AWSAccessKeyId'),
        :signature => {
          :version => params.delete('SignatureVersion'),
          :value => params.delete('Signature'),
          :method => params.delete('SignatureMethod'),
          :timestamp => params.delete('Timestamp')
        }
      }
      @expiration = params.delete('Expires')
      @parameters = params
    end

    def valid_actions
      ActionHandler::mappings.keys
    end

    def valid_action?
      return false if @action == :unknown
      return false unless valid_actions.include?(@action)
      true
    end


  end

end
