module Vcloud
  module Core
    class IndependentDisk

      class QueryExecutionError < RuntimeError; end
      class DiskNotFoundException < RuntimeError; end
      class MultipleDisksFoundException < RuntimeError; end
      class DiskAlreadyExistsException < RuntimeError; end

      attr_reader :id

      # Return an object referring to a particular IndependentDisk
      #
      # @param id [String] The ID of the independent disk
      # @return [Vcloud::Core::IndependentDisk]
      def initialize(id)
        unless id =~ /^[-0-9a-f]+$/
          raise "IndependentDisk id : #{id} is not in correct format"
        end
        @id = id
      end

      # Return the ID of an IndependentDisk referred to by name and vDC
      #
      # @param name [String] The name of the disk
      # @param vdc [String] The name of the vDC
      # @return [Vcloud::Core::IndependentDisk] An object representing the IndependentDisk
      def self.get_by_name_and_vdc_name(name, vdc_name)
        q = Vcloud::Core::QueryRunner.new
        query_results = q.run('disk', :filter => "name==#{name};vdcName==#{vdc_name}")
        unless query_results
          raise QueryExecutionError,
            "Error finding IndependentDisk by name '#{name}' & vdc '#{vdc_name}'"
        end
        raise DiskNotFoundException,
          "IndependentDisk '#{name}' not found in vDC '#{vdc_name}'" if query_results.size == 0
        if query_results.size > 1
          raise MultipleDisksFoundException,
            "Multiple IndependentDisks matching '#{name}' found in vDC '#{vdc_name}. " +
                "Create disks via IndependentDisk.new(disk_id) instead."
        end
        return self.new(query_results.first[:href].split('/').last)
      end

      # Create a named, sized IndependentDisk in a particular named vDC
      #
      # @param vdc [String] The name of the vDC
      # @param name [String] The name of the IndependentDisk
      # @param size [String, Integer] The size as an integer of bytes, or an
      #                               integer with units
      #                               (see convert_size_to_bytes)
      # @return [Vcloud::Core::IndependentDisk] An object representing
      #                                         the new disk
      def self.create(vdc, name, size)
        vdc_name = vdc.name
        begin
          self.get_by_name_and_vdc_name(name, vdc_name)
        rescue DiskNotFoundException
          ok_to_create = true
        end

        unless ok_to_create
          raise DiskAlreadyExistsException,
            "Cannot create Independent Disk '#{name}' in vDC '#{vdc_name}' - a disk with " +
            "that name is already present"
        end

        size_in_bytes = convert_size_to_bytes(size)
        body = Vcloud::Core::Fog::ServiceInterface.new.post_create_disk(vdc.id, name, size_in_bytes)
        return self.new(body[:href].split('/').last)
      end

      # Return all the vcloud attributes of IndependentDisk
      #
      # @return [Hash] a hash describing all the attributes of disk
      def vcloud_attributes
        Vcloud::Core::Fog::ServiceInterface.new.get_disk(id)
      end

      # Return the name of IndependentDisk
      #
      # @return [String] the name of instance
      def name
        vcloud_attributes[:name]
      end

      # Return the href of IndependentDisk
      #
      # @return [String] the href of instance
      def href
        vcloud_attributes[:href]
      end

      # Return an array of Vcloud::Core::Vm objects which are attached to
      # independent disk
      #
      # @return [Array] an array of Vcloud::Core::Vm
      def attached_vms
        body = Vcloud::Core::Fog::ServiceInterface.new.get_vms_disk_attached_to(id)
        vms = body.fetch(:VmReference)
        vms.map do |vm|
          id = vm.fetch(:href).split('/').last
          parent_vapp = Vcloud::Core::Vapp.get_by_child_vm_id(id)
          Vcloud::Core::Vm.new(id, parent_vapp)
        end
      end

      # Convert an integer and units suffix (e.g. 10mb) into an integer of bytes
      # Allowed suffixes are: mb, gb, mib, gib
      #
      # @param size [String] the intended size of the disk (optionally with units)
      # @return [Integer] the disk size in bytes
      def self.convert_size_to_bytes(size)
        if size.to_s =~ /^(\d+)mb$/i
          Integer($1) * (10**6)
        elsif size.to_s =~ /^(\d+)gb$/i
          Integer($1) * (10**9)
        elsif size.to_s =~ /^(\d+)mib$/i
          Integer($1) * (2**20)
        elsif size.to_s =~ /^(\d+)gib$/i
          Integer($1) * (2**30)
        elsif size.to_s =~ /^(\d+)$/i
          Integer($1)
        else
          raise ArgumentError, "Cannot convert size string '#{size}' into a number of bytes"
        end
      end

    end
  end
end