module MusicBrainz
  class BaseModel
    def self.inherited(klass)
      klass.send(:include, InstanceMethods)
      klass.send(:extend, ClassMethods)
    end

    module ClassMethods
      def field(name, type)
        fields[name] = type
        attr_reader name

        define_method("#{name}=") do |val|
          instance_variable_set("@#{name}", validate_type(val, type))
        end
      end

      def fields
        instance_variable_set(:@fields, {}) unless instance_variable_defined?(:@fields)
        instance_variable_get(:@fields)
      end

      def client
        MusicBrainz.client
      end

			def find(hash)
				underscored_name = underscore_name.to_sym
				client.load(underscored_name, hash, { binding: underscored_name, create_model: underscored_name })
			end

			def search(hash)
				hash = escape_strings(hash)
				query_val = build_query(hash)
				underscored_name = underscore_name
				client.load(underscored_name.to_sym, { query: query_val, limit: 10 }, { binding: underscored_name.insert(-1,"_search").to_sym })
			end

			class ::String
				def underscore
					self.gsub(/::/, '/').
					gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
					gsub(/([a-z\d])([A-Z])/,'\1_\2').
					tr("-", "_").
					downcase
				end
			end

			def build_query(hash)
				return ["#{hash.first[0].to_s}:\"#{hash.first[1]}\""] if hash.size ==1
				arr ||= []
				hash.each { |k, v| arr << "#{k.to_s}:\"#{hash[k]}\"" }
				arr.join(' AND ')
			end

			def escape_strings(hash)
				hash.each { |k, v| hash[k] = CGI.escape(v).gsub(/\!/, '\!') }
				hash
			end

			def underscore_name
				# self.name[13..-1] => removes MusicBrainz::
				self.name[13..-1].underscore
			end

			# these probably should be private... but I'm not sure how to get it to work in a module...
			# private_class_method :build_query, :escape_strings
    end

    module InstanceMethods
      def initialize(params = {})
        params.each do |field, value|
          self.send(:"#{field}=", value)
        end
      end

      def client
        MusicBrainz.client
      end

    private

      def validate_type(val, type)
        if type == Integer
          val.to_i
        elsif type == Float
          val.to_f
        elsif type == String
          val.to_s
        elsif type == Date
          val = if val.nil? or val == ""
            [2030, 12, 31]
          elsif val.split("-").length == 1
            [val.split("-").first.to_i, 12, 31]
          elsif val.split("-").length == 2
            val = val.split("-").map(&:to_i)
            [val.first, val.last, -1]
          else
            val.split("-").map(&:to_i)
          end

          Date.new(*val)
        elsif type == Array
          Array(val)
        else
          val
        end
      end
    end
  end
end
