module JsonSchemaRails
  class SchemaValidator
    DEFAULT_SCHEMA_URI = 'http://json-schema.org/draft-04/schema'

    attr_reader :meta_schemas

    def initialize
      @meta_schemas = MetaSchemas.new
      @loader = Loader.new
    end

    def use_core_schemas!(options = {})
      schemas_dir = File.expand_path('../../../schemas', __FILE__)
      if options[:loose]
        add_meta_schema_file Dir[File.join(schemas_dir, '*.json')]
      else
        add_meta_schema_file Dir[File.join(schemas_dir, 'strict', '*.json')]
      end
    end

    def validate(schema_file)
      content = @loader.render_schema_content(schema_file)

      meta_schema_uri = content["$schema"] || content[:"$schema"] || DEFAULT_SCHEMA_URI
      unless meta_schema_uri
        raise JsonSchemaRails::SchemaNotFound, "No $schema specified"
      end

      meta_schema = @meta_schemas.lookup!(meta_schema_uri)
      valid, errors = meta_schema.validate(content)
      raise ValidationError.from_errors(errors, meta_schema) unless valid
      true
    end

    protected

    def add_meta_schema_file(*meta_schema_files)
      meta_schema_files.flatten.each do |file|
        @meta_schemas << @loader.load_schema!(file)
      end
      self
    end

    class MetaSchemas
      include Enumerable

      def initialize
        @store = JsonSchema::DocumentStore.new
        @expander = JsonSchema::ReferenceExpander.new
      end

      def <<(schema)
        Array(schema).each do |file|
          schema.expand_references!(store: @store)
          @store.add_schema(schema)
        end
      end

      def [](schema_uri)
        @store.lookup_schema(schema_uri.sub(/#+$/, ''))
      end

      def lookup!(schema_uri)
        self[schema_uri] or
          raise JsonSchemaRails::SchemaNotFound, "Unknown $schema: #{schema_uri}"
      end

      def each
        @store.each { |k, v| yield v }
      end

      def size
        reduce(0) { |sum, n| sum + 1 }
      end
    end

    class Loader < Loaders::Base
      def initialize
        super(cache: false)
      end

      def load_schema!(schema_path)
        load_schema_file(schema_path)
      end

      def render_schema_content(schema_path)
        load_schema_content(schema_path)
      end
    end
  end
end