# -*- encoding: utf-8 -*-

require 'webgen/content_processor'
require 'webgen/path'
webgen_require 'sass'

module Webgen
  class ContentProcessor

    # Processes content in Sass markup (used for writing CSS files).
    module Sass

      # Custom importer for Sass to load files from the file system but resolves absolute paths from
      # the given root directory and not from the filesystem root!
      class FileSystemImporter < ::Sass::Importers::Filesystem

        def find_real_file(dir, name, options = {}) #:nodoc:
          for (f,s) in possible_files(remove_root(name))
            if full_path = Dir["#{dir}/#{f}"].first
              full_path.gsub!(REDUNDANT_DIRECTORY,File::SEPARATOR)
              return full_path, s
            end
          end
          nil
        end

      end

      # Custom importer for Sass to load files by resolving them in the node tree.
      class NodeTreeImporter < ::Sass::Importers::Base

        # Creates a new importer that imports files from the node tree relative to the given node alcn.
        def initialize(context)
          @context = context
        end

        def marshal_dump #:nodoc:
        end

        def marshal_load(data) #:nodoc:
        end

        # @see Base#find_relative
        def find_relative(name, base, options)
          _find(base, name, options)
        end

        # @see Base#find
        def find(name, options)
          _find(@context.ref_node.alcn, name, options)
        end

        def mtime(name, options) #:nodoc:
          node = resolve_node(@context.ref_node.alcn, name)
          node['modified_at'] if node
        end

        def key(name, options) #:nodoc:
          ["webgen:", name]
        end

        def to_s #:nodoc:
          "webgen: #{@context.ref_node.alcn}"
        end

        #######
        private
        #######

        # Find the @import-ed name under the given base filename.
        #
        # Returns a Sass::Engine object if found or +nil+ otherwise.
        def _find(base, name, options)
          node, syntax = resolve_node(base, name)
          return unless node

          @context.website.ext.item_tracker.add(@context.dest_node, :node_content, node)
          options[:syntax] = syntax
          options[:filename] = node.alcn
          options[:importer] = self
          ::Sass::Engine.new(node.node_info[:path].data, options)
        end

        # Resolve the path using the given base filename.
        #
        # Returns [node, syntax] if a node was found or nil otherwise
        def resolve_node(base, path)
          possible_filenames(path).each do |filename, syntax|
            node = @context.website.tree.resolve_node(Webgen::Path.append(base, filename), nil)
            return [node, syntax] if node
          end
          nil
        end

        # Return an array of all possible (filename, syntax) pairs for the given path.
        def possible_filenames(path)
          dirname, basename = File.split(path)
          basename, ext = basename.scan(/^(.*?)(?:\.(sass|scss))?$/).first
          (ext.nil? ? %w{sass scss} : [ext]).map do |iext|
            [["#{dirname}/_#{basename}.#{iext}", iext.to_sym], ["#{dirname}/#{basename}.#{iext}", iext.to_sym]]
          end.flatten(1)
        end

      end

      module ::Sass::Script::Functions

        # Return the correct relative path for the given path.
        def relocatable(path)
          assert_type(path, :String)
          context = options[:webgen_context]
          path = path.value

          ref_node = context.website.tree[options[:filename]]
          ref_node = context.website.tree.root if ref_node.nil? && path[0] == ?/

          if ref_node
            if dest_node = ref_node.resolve(path, context.dest_node.lang, true)
              context.website.ext.item_tracker.add(context.dest_node, :node_meta_info, dest_node)
              path = context.dest_node.route_to(dest_node)
            end
          else
            context.website.logger.warn { "Couldn't determine reference node for resolving node in Sass file." }
          end
          ::Sass::Script::String.new(path, :string)
        end
        declare :relocatable, [:string]

      end

      # Convert the content in +sass+ markup to CSS.
      def self.call(context)
        context.content = ::Sass::Engine.new(context.content, default_options(context)).render
        context
      rescue ::Sass::SyntaxError => e
        raise Webgen::RenderError.new(e, 'content_processor.sass', context.dest_node, nil, (e.sass_line if e.sass_line))
      end

      def self.default_options(context) # :nodoc:
        opts = context.website.config['content_processor.sass.options']
        load_paths = context.website.ext.sass_load_paths + [NodeTreeImporter.new(context)]
        opts.merge({
                     :filename => context.ref_node.alcn,
                     :syntax => :sass,
                     :cache_store => ::Sass::CacheStores::Filesystem.new(context.website.tmpdir('content_processor.sass')),
                     :filesystem_importer => FileSystemImporter,
                     :load_paths => load_paths,
                     :webgen_context => context
                   })
      end

    end

  end
end
