module Innate

  # This is a container module for wrappers of templating engines and handles
  # lazy requiring of needed engines.
  module View
    include Optioned

    ENGINE, TEMP = {}, {}

    options.dsl do
      o "Cache compiled templates",
        :cache, true

      o "Cache template files after they're read to prevent additional filesystem calls",
        :read_cache, false
    end

    # In order to be able to render actions without running
    # Innate::setup_dependencies we have to add the cache here already.
    Cache.add(:view)

    module_function

    def compile(string)
      return yield(string.to_s) unless View.options.cache
      string = string.to_s
      checksum = Digest::MD5.hexdigest(string)
      Cache.view[checksum] ||= yield(string)
    end

    def exts_of(engine)
      name = engine.to_s
      ENGINE.reject{|ext, klass| klass != name }.keys
    end

    # Try to obtain given engine by its registered name.
    def get(engine)
      if klass = TEMP[engine]
        return klass
      elsif klass = ENGINE[engine]
        TEMP[engine] = obtain(klass)
      else
        TEMP[engine] = obtain(engine, View)
      end
    end

    # We need to put this in a Mutex because simultanous calls for the same
    # class will cause race conditions and one call may return the wrong class
    # on the first request (before TEMP is set).
    # No mutex is used in Fiber environment, see Innate::State and subclasses.
    def obtain(klass, root = Object)
      Innate.sync do
        view_name = /^#{klass.to_s.downcase.dup.delete('_')}$/i
        if view = View.constants.grep(view_name).first
          root.const_get(view)
        else
          raise(NameError, "View #{klass} not found")
        end
      end
    end

    # Reads the specified view template from the filesystem. When the read_cache
    # option is enabled, templates will be cached to prevent unnecessary
    # filesystem reads in the future.
    #
    # @example usage
    #
    #   View.read('some/file.xhtml')
    #
    # @param [#to_str] view
    #
    # @api private
    # @see Action#render
    def read(view)
      return Cache.view[view] ||= ::File.read(view) if View.options.read_cache
      ::File.read(view)
    end

    # Register given templating engine wrapper and extensions for later usage.
    #
    # +name+ : the class name of the templating engine wrapper
    # +exts+ : any number of arguments will be turned into strings via #to_s
    #          that indicate which filename-extensions the templates may have.
    def register(klass, *exts)
      exts.each do |ext|
        ext = ext.to_s
        engine = ENGINE[ext]
        Log.warn("overwriting %p, was set to %p" % [ext, engine]) if engine
        ENGINE[ext] = klass
      end
    end

    autoload :None,   'innate/view/none'
    autoload :ERB,    'innate/view/erb'
    autoload :Etanni, 'innate/view/etanni'

    register 'Innate::View::None',   :css, :html, :htm
    register 'Innate::View::ERB',    :erb
    register 'Innate::View::Etanni', :xhtml
  end
end
