module Apipie

  def self.prop(name, expected_type, options = {}, sub_properties = [])
    Apipie::ResponseDescriptionAdapter::PropDesc.new(name, expected_type, options, sub_properties)
  end

  def self.additional_properties(yesno)
    Apipie::ResponseDescriptionAdapter::AdditionalPropertiesModifier.new(yesno)
  end

  class ResponseDescriptionAdapter
    class Modifier
      def apply(adapter)
        raise "Modifier subclass must implement 'apply' method"
      end
    end

    class AdditionalPropertiesModifier < Modifier
      def initialize(additional_properties_allowed)
        @additional_properties_allowed = additional_properties_allowed
      end

      def apply(adapter)
        adapter.additional_properties =  @additional_properties_allowed
      end
    end
  end


  class ResponseDescriptionAdapter

    #
    # A ResponseDescriptionAdapter::PropDesc object pretends to be an Apipie::Param in a ResponseDescription
    #
    # To successfully masquerade as such, it needs to:
    #    respond_to?('name') and/or ['name'] returning the name of the parameter
    #    respond_to?('required') and/or ['required'] returning boolean
    #    respond_to?('additional_properties') and/or ['additional_properties'] returning boolean
    #    respond_to?('validator') and/or ['validator'] returning 'nil' (so type is 'string'), or an object that:
    #           1) describes a type.  currently type is inferred as follows:
    #                 if validator.is_a? Apipie::Validator::EnumValidator -->  respond_to? 'values' (returns array).  Type is enum or boolean
    #                 else: use v.expected_type().  This is expected to be the swagger type, or:
    #                     numeric ==> swagger type is 'number'
    #                     hash ==> swagger type is 'object' and validator should respond_to? 'params_ordered'
    #                     array ==> swagger type is array and validator (FUTURE) should indicate type of element

    class PropDesc

      def to_s
        "PropDesc -- name: #{@name}  type: #{@expected_type} required: #{@required} options: #{@options} subprop count: #{@sub_properties.length} additional properties: #{@additional_properties}"
      end

      #
      # a ResponseDescriptionAdapter::PropDesc::Validator pretends to be an Apipie::Validator
      #
      class Validator
        attr_reader :expected_type

        def [](key)
          self.send(key) if self.respond_to?(key.to_s)
        end

        def initialize(expected_type, enum_values = nil, sub_properties = nil)
          @expected_type = expected_type
          @enum_values = enum_values
          @is_enum = !!enum_values
          @sub_properties = sub_properties
        end

        def is_enum?
          !!@is_enum
        end

        def values
          @enum_values
        end

        def params_ordered
          raise "Only validators with expected_type 'object' can have sub-properties" unless @expected_type == 'object'
          @sub_properties
        end
      end

      #======================================================================


      def initialize(name, expected_type, options = {}, sub_properties = [])
        @name = name
        @required = true
        @required = false if options[:required] == false
        @expected_type = expected_type
        @additional_properties = false

        options[:desc] ||= options[:description]
        @description = options[:desc]
        @options = options
        @is_array = options[:is_array] || false
        @sub_properties = []
        for prop in sub_properties do
          add_sub_property(prop)
        end
      end

      def [](key)
        self.send(key) if self.respond_to?(key.to_s)
      end

      def add_sub_property(prop_desc)
        raise "Only properties with expected_type 'object' can have sub-properties" unless @expected_type == 'object'
        case prop_desc
        when PropDesc
          @sub_properties << prop_desc
        when Modifier
          prop_desc.apply(self)
        else
          raise "Unrecognized prop_desc type (#{prop_desc.class})"
        end
      end

      def to_json(lang)
        {
            name: name,
            required: required,
            validator: validator,
            description: description,
            additional_properties: additional_properties,
            is_array: is_array?,
            options: options
        }
      end
      attr_reader :name, :required, :expected_type, :options, :description
      attr_accessor :additional_properties

      alias desc description

      def is_array?
        @is_array
      end

      def validator
        Validator.new(@expected_type, options[:values], @sub_properties)
      end
    end
  end

  #======================================================================

  class ResponseDescriptionAdapter

    def self.from_self_describing_class(cls)
      adapter = ResponseDescriptionAdapter.new(cls.to_s)
      props = cls.describe_own_properties
      adapter.add_property_descriptions(props)
      adapter
    end

    def initialize(typename)
      @property_descs = []
      @additional_properties = false
      @typename = typename
    end

    attr_accessor :additional_properties, :typename

    def allow_additional_properties
      additional_properties
    end

    def to_json
      params_ordered.to_json
    end

    def add(prop_desc)
      case prop_desc
      when PropDesc
        @property_descs << prop_desc
      when Modifier
        prop_desc.apply(self)
      else
        raise "Unrecognized prop_desc type (#{prop_desc.class})"
      end
    end

    def add_property_descriptions(prop_descs)
      for prop_desc in prop_descs
        add(prop_desc)
      end
    end

    def property(name, expected_type, options)
      @property_descs << PropDesc.new(name, expected_type, options)
    end

    def params_ordered
      @property_descs
    end

    def is_array?
      false
    end
  end
end
