module DDE

  module Errors                             # :nodoc:
    def self.[](error_code)
      Win::DDE::ERRORS[error_code]
    end

    class InitError < RuntimeError          # :nodoc:
    end
    class FormatError < RuntimeError        # :nodoc:
    end
    class StringError < RuntimeError        # :nodoc:
    end
    class ServiceError < RuntimeError        # :nodoc:
    end
    class ClientError < RuntimeError        # :nodoc:
    end
  end

  # Class encapsulates DDE application. DDE::App serves as a base for more specific types,
  # such as DDE::Server or DDE:: Client.
  class App
    include Win::DDE

    attr_reader :id, :init_flags

    # Creates new DDE application and starts DDE instance
    # if dde_callback block is attached
    def initialize( init_flags=nil, &dde_callback )
      @init_flags = init_flags

      start_dde init_flags, &dde_callback if dde_callback

      # todo: Destructor to ensure Dde instance is uninitialized and string handles freed (is it even working?)
      #ObjectSpace.define_finalizer self, ->(id) { stop_dde }
    end

    # (Re)Initialize application with DDEML library, providing attached dde callback
    # either preserved @init_flags or init_flags argument are used
    def start_dde( init_flags=nil, &dde_callback )
      @init_flags = init_flags || @init_flags || APPCLASS_STANDARD

      try "Starting DDE" do
        @id, status = dde_initialize @id, @init_flags, &dde_callback
        error(status) unless @id && status == DMLERR_NO_ERROR
      end
    end

    # (Re)Initialize application with DDEML library, providing attached dde callback
    def stop_dde
      try "Stopping DDE" do
        error unless @id && dde_uninitialize(@id) # Uninitialize app with DDEML library
        @id = nil                                 # Clear instance id if uninitialization successful
      end
    end

    # Expects a block, yields to it inside a rescue block, raises given error_type with extended fail message.
    # Returns self in case of success (to enable method chaining).
    def try( action, error_type=DDE::Errors::InitError )
      begin
        yield
      rescue => e
        raise error_type, action + " failed with: #{e}"
      end
      self
    end

    # Raises Runtime error with message based on given message (DdeGetLastError message if no message given)
    def error( message = nil )
      raise case message
        when Integer
          DDE::Errors[message]
        when nil
          DDE::Errors[dde_get_last_error(@id)]
        else
          message
      end
    end

    def dde_active?
      !!@id
    end

  end
end