class Rad::Template
  inject logger: :logger, environment: :environment

  attr_accessor :relative_path_resolver, :prefixes
  require_attr :prefixes
  
  def initialize
    @relative_path_resolver = RelativePathResolver.new
  end
  
  def render *args, &block
    result, context = basic_render(parse_arguments(*args), &block)
    result
  end        
  
  def exist? tname, options = {}
    !!find_template(tname, options)
  end
  
  def read tname, options = {}
    file = find_template(tname, options) || raise("no template '#{tname}'!")
    File.read file
  end
  
  def basic_render options, &block
    puts rad.template.paths
    with_context options do |context|
      context.content_block ||= block

      with_scope options, context do |scope|              
        file = (
          options[:file] || 
          find_template(options[:template], options.merge(scope)) ||
          (options[:if_not_exist] && find_template(options[:if_not_exist], options.merge(scope))) ||
          raise("no template '#{options[:template]}'!")
        )
        
        scope[:current_dir] = dirname(file)
        
        template = create_tilt_template file
        
        result = with_template context, template do
          render_template template, context, options, &context.content_block
        end
        
        return result, context
      end            
    end
  end
  
  def parse_arguments *args
    options = args.extract_options!
    if args.size == 1
      options[:template] = args.first
    else 
      raise "Invalid input" if args.size != 0
    end     

    options
  end
  
  def find_file tname, prefixes, format, exact_format, directories
    prefixes = prefixes || self.prefixes
    prefixes.each do |prefix|
      tname = template_name_with_prefix("#{directory_name}#{tname}", prefix)
      file = if tname.include? '.'
        _find_file(tname, directories) || _find_file("#{tname}.*", directories)
      else            
        if format
          _find_file("#{tname}.#{format}.*", directories) or
            _find_file("#{tname}.*", directories, exact_format)
        else
          _find_file("#{tname}.*", directories)
        end
      end
      return file if file
    end
    return nil
  end
  
  def directory_name; "" end
  
  def paths; @paths ||= [] end
  
  protected            
    # with_one_extension - 'tname.*' matches not only 'tname.erb' but also 'tname.html.erb', 
    # with this option enabled it will not match 'tname.html.erb', only 'tname.erb'
    def _find_file pattern, directories, with_one_extension = false      
      files = rad.environment.find_files_by_pattern_without_cache pattern, directories
      files = files.select{|f| f !~ /\.[^\.\/]+\.[^\.\/]+$/} if with_one_extension          
      raise "multiple templates for '#{pattern}'!" if files.size > 1
      files.first      
    end
    
    def template_name_with_prefix tname, prefix      
      index = tname.rindex('/')
      index = index ? index + 1 : 0
      tname = tname.clone
      tname.insert index, prefix
    end
    
    def with_context options, &block
      context = Thread.current[:render_context] || options[:context] || Context.new(options[:instance_variables])
      
      old = Thread.current[:render_context]
      begin            
        Thread.current[:render_context] = context
        block.call context
      ensure
        Thread.current[:render_context] = old
      end
    end
    
    def with_scope options, context, &block
      initial = context.scope_variables

      old = context.scope_variables || OpenObject.new
      begin
        context.scope_variables = {
          current_dir: (options[:current_dir] || old[:current_dir]),
          format: (options[:format] || old[:format]),
          relative_path_resolver: (options[:relative_path_resolver] || old[:relative_path_resolver])
        }

        block.call context.scope_variables
      ensure
        context.scope_variables = old if initial
      end
    end
    
    def with_template context, template, &block
      old = context._tilt_template
      begin            
        context._tilt_template = template
        block.call context
      ensure
        context._tilt_template = old
      end
    end
    
    def dirname path; 
      File.dirname path
    end        
    cache_method_with_params_in_production :dirname                    
    
    def find_template tname, options
      tname.must_be.a String
      # splitted into two to optimize cache
      if tname =~ /^\//
        find_absolute_template tname, options[:prefixes], options[:format], options[:exact_format]
      else
        resolver = options[:relative_path_resolver] || relative_path_resolver
        resolver.find_relative_template tname, options[:prefixes], options[:format], options[:exact_format], options[:current_dir]
      end
    end
            
    def find_absolute_template tname, prefixes, format, exact_format                    
      prefixes = prefixes || self.prefixes
      find_file(tname, prefixes, format, exact_format, paths)
    end
    cache_method_with_params_in_production :find_absolute_template                
    
    def create_tilt_template path      
      Tilt.new(path, nil, ugly: true, outvar: "@output"){|t| File.read(t.file)}
    end
    cache_method_with_params_in_production :create_tilt_template
    
    def render_template template, context, options, &block
      locals = options[:locals] || {}
      if object = options[:object]
        locals[:object] = object
      end

      template.render context, locals, &block
    end        
end