module CloudProviders
  class Ec2Instance < RemoteInstance
    
    default_options(
      :security_groups => [],
      :private_ip => nil,
      :dns_name => nil,
      :instance_type => nil,
      :public_ip => nil,
      :key_name => nil,
      :launch_time => nil,
      :availability_zones => [],
      :block_device_mapping => [{}],
      :disable_api_termination => nil,
      :instance_initiated_shutdown_behavior => nil,
      :subnet_id => nil
    )
    
    def initialize(raw_response={})
      @raw_response = raw_response
      self.instance_id = raw_response["instanceId"] rescue nil
      self.security_groups = raw_response.groupSet.item[0].groupId rescue nil
      self.image_id = raw_response["imageId"] rescue nil
      self.private_ip = raw_response["privateIpAddress"] rescue nil
      self.dns_name = raw_response["dnsName"] rescue nil
      self.instance_type = raw_response["instanceType"] rescue nil
      self.public_ip = raw_response["ipAddress"] rescue nil
      self.key_name = raw_response["keyName"] rescue nil
      self.launch_time = raw_response["launchTime"] rescue nil
      self.availability_zones = raw_response["placement"]["availabilityZone"] rescue nil
      self.status = raw_response["instanceState"]["name"] rescue nil
      self.block_device_mapping = raw_response["blockDeviceMapping"] rescue nil
      self.disable_api_termination = raw_response["disableApiTermination"] rescue nil
      self.instance_initiated_shutdown_behavior = raw_response["instance_initiated_shutdown_behavior"] rescue nil
      self.subnet_id = raw_response["subnetId"] rescue nil
      super
    end
    
    def keypair(n=nil)
      @keypair ||= Keypair.new(self.key_name)
    end
    
    def zone
      availability_zones.first
    end
    
    def reachable?
      ping_port self.public_ip, 22
    end

    def ssh_available?
      cloud.security_groups.map {|a|
        a.authorizes.map {|t| t.from_port.to_i }.flatten
      }.flatten.include?(22) and
        reachable? and
        in_service? and
        keypair and keypair.exists?
    end
    
    def in_service?
      running?
    end
    
    def run!
      r = cloud_provider.ec2.run_instances(:image_id => image_id,
      :min_count => min_count,
      :max_count => max_count,
      :key_name => keypair.basename,
      :security_group => cloud.security_group_names,
      :user_data => user_data,
      :instance_type => instance_type,
      :availability_zone => availability_zone,
      :block_device_mapping => block_device_mapping,
      :base64_encoded => true)
      r.instancesSet.item.map do |i|
        inst_options = i.merge(r.merge(:cloud => cloud)).merge(cloud.cloud_provider.dsl_options)
        Ec2Instance.new(inst_options)
      end.first
    end
    def self.run!(hsh); new(hsh).run!; end
    
    def terminate!
      cloud_provider.ec2.terminate_instances(:instance_id => [self.instance_id])
      cloud_provider.reset!
    end
    def self.terminate!(hsh={}); new(hsh).terminate!; end
    
    # list of directories and files to exclude when bundling an instance
    def rsync_excludes(array_of_abs_paths_to_exclude=nil)
      array_of_abs_paths_to_exclude ||= %w( /sys
            /proc
            /dev/pts
            /dev
            /media
            /mnt
            /proc
            /sys
            /etc/ssh/ssh_host_*
            /etc/ssh/moduli
            /etc/udev/rules.d/70-persistent-net.rules
            /etc/udev/rules.d/z25_persistent-net.rules
            )
      array_of_abs_paths_to_exclude.inject(''){|str, path| str<<"--exclude=#{path}"; str}
    end

    # create an image file and copy this instance to the image file.
    def make_image(opts={})
      opts = {:volume       => '/',
              :size         => 6000,
              :destination  => '/mnt/bundle',
              :exclude      => nil
              }.merge(opts)
      image_file = File.join(opts[:destination], opts[:prefix] )
      cmds = ["mkdir -p #{opts[:destination]}"]
      cmds << "dd if=/dev/zero of=#{image_file} bs=1M count=#{opts[:size]}"
      cmds << "mkfs.ext3 -F -j #{image_file}"
      cmds << "mkdir -p #{opts[:destination]}/loop"
      cmds << "mount -o loop #{image_file} #{opts[:destination]}/loop"
      cmds << "rsync -ax #{rsync_excludes(opts[:exclude])} #{opts[:volume]}/ #{opts[:destination]}/loop/"
      cmds << "if [[ -f /etc/init.d/ec2-ssh-host-key-gen ]]; then chmod u+x /etc/init.d/ec2-ssh-host-key-gen ;fi"
      cmds << "umount #{opts[:destination]}/loop"
      self.ssh cmds
      image_file
    end

    # TODO: WIP:  bundle up the instance and register it as a new ami.
    # An image of the running node will be creatd, or
    # if a path to an image file on the remote node is given, that will be used
    def bundle_and_register(img=nil, opts={})
      opts = {:cert         => cert,
              :bucket       => nil,
              :prefix       => image_id,
              :kernel       => kernel_id,
              :ramdisk      => ramdisk_id,
              :ec2cert      => cloud_cert
              }.merge(opts)
      raise "You must specify a bucket to bundle to" if opts[:bucket].nil?
      scp ec2cert, "/mnt/bundle/"
      scp cert, "/mnt/bundle/"
      arch = self[:instanceType].match(/m1\.small|c1\.medium/) ? 'i386' : 'x86_64'
      image = img ? img : make_image(opts) 
      ssh "ec2-bundle-image #{image} -d /mnt/bundle -u #{self[:ownerId]} -k /mnt/bundle/pk-*.pem -c /tmp/cert-*.pem"
      manifest = "/mnt/bundle/#{opts[:prefix]}.manifest.xml"
      ssh "ec2-upload-bundle -a #{access_key} -s #{secret_access_key} -m #{manifest}"
      ami_str = ssh "ec2-register-bundle"
      ami = ami_str.grep(/ami-\w*/).first
      return ami
    end
        
  end
end