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

require 'pathname'
require 'webgen/languages'
require 'webgen/path'

module Webgen

  # Represents a file, a directory or a fragment. A node always belongs to a Tree.
  #
  # All needed meta and processing information is associated with the node itself. The meta
  # information is available through the #[] and #meta_info accessors, the internal processing
  # information through the #node_info accessor.
  #
  # This class is not directly used. Instead path handlers define sub-classes that provide handler
  # specific methods. See the basic sub-class Webgen::PathHandler::Base::Node.
  class Node

    # The parent node. This is in all but one case a Node object. The one exception is that the
    # parent of the Tree#dummy_node is a Tree object.
    attr_reader :parent

    # The child nodes of this node.
    attr_reader :children

    # The canonical name of this node.
    attr_reader :cn

    # The localized canonical name of this node.
    attr_reader :lcn

    # The absolute canonical name of this node.
    attr_reader :acn

    # The absolute localized canonical name of this node.
    attr_reader :alcn

    # The level of the node. The level specifies how deep the node is in the hierarchy.
    attr_reader :level

    # The language of this node.
    attr_reader :lang

    # Meta information associated with the node. If you need just a value for a meta information
    # key, use the #[] method.
    attr_reader :meta_info

    # Return the node information hash which contains information for processing the node.
    attr_reader :node_info

    # The full destination path of this node.
    attr_reader :dest_path

    # The tree to which this node belongs.
    attr_reader :tree


    # Create a new Node instance.
    #
    # [+parent+ (immutable)]
    #    The parent node under which this nodes should be created.
    #
    # [+cn+ (immutable)]
    #    The canonical name for this node. Needs to be of the form 'basename.ext' or 'basename'
    #    where +basename+ does not contain any dots. Also, the +basename+ must not include a
    #    language part!
    #
    # [+dest_path+ (immutable)]
    #    The full output path for this node. If this node is a directory, the path must have a
    #    trailing slash ('dir/'). If it is a fragment, it has to include a hash sign. This can also
    #    be an absolute path like http://example.com.
    #
    # [+meta_info+]
    #    A hash with meta information for the new node.
    #
    # The language of a node is taken from the meta information +lang+ and the entry is deleted from
    # the meta information hash. The language cannot be changed afterwards! If no +lang+ key is
    # found, the node is unlocalized.
    def initialize(parent, cn, dest_path, meta_info = {})
      @parent = parent
      @children = []
      @cn = cn.freeze
      @dest_path = dest_path.freeze

      @lang = Webgen::LanguageManager.language_for_code(meta_info.delete('lang'))
      @lang = nil unless is_file?

      @lcn = Webgen::Path.lcn(@cn, @lang).freeze
      @acn = (@parent.kind_of?(Webgen::Node) ? @parent.acn.sub(/#.*$/, '') + @cn : '').freeze
      @alcn = (@parent.kind_of?(Webgen::Node) ? @parent.alcn.sub(/#.*$/, '') + @lcn : '').freeze

      @meta_info = meta_info
      @node_info = {}

      @level = -1
      @tree = @parent
      while @tree.kind_of?(Webgen::Node)
        @level += 1
        @tree = @tree.parent
      end

      @tree.register_node(self)
      @parent.children << self unless @parent == @tree
    end

    # Return the meta information item for +key+.
    def [](key)
      @meta_info[key]
    end

    # Check if the node is a directory.
    def is_directory?
      @cn[-1] == ?/ && !is_fragment?
    end

    # Check if the node is a file.
    def is_file?
      !is_directory? && !is_fragment?
    end

    # Check if the node is a fragment.
    def is_fragment?
      @cn[0] == ?#
    end

    # Check if the node is the root node.
    def is_root?
      self == tree.root
    end

    # Check if the this node is an ancestor of +node+.
    #
    # The check is performed using only the +parent+ information of the involved nodes, NOT the
    # actual alcn values!
    def is_ancestor_of?(node)
      node = node.parent
      node = node.parent while node != tree.dummy_root && node != self
      node != tree.dummy_root
    end

    # Return the string representation of the node which is just the #alcn.
    def to_s
      @alcn
    end

    def inspect #:nodoc:
      "<##{self.class.name}: alcn=#{@alcn}>"
    end

    # Return +true+ if the #alcn matches the pattern.
    #
    # See Webgen::Path.matches_pattern? for more information.
    def =~(pattern)
      Webgen::Path.matches_pattern?(@alcn, pattern)
    end

    # Return the node representing the given +path+ in the given language.
    #
    # The path can be absolute (i.e. starting with a slash) or relative to the current node.
    # Relative paths are made absolute by using the #alcn of the current node.
    #
    # If the +lang+ parameter is not used, it defaults to the language of the current node.
    #
    # See Tree#resolve_node for detailed information on how the correct node for the path is found
    # and for the +msg_on_failure+ parameter.
    def resolve(path, lang = @lang, msg_on_failure = false)
      @tree.resolve_node(Webgen::Path.append(@alcn, path), lang, msg_on_failure)
    end

    # Return the relative path to the given node.
    #
    # If the +lang+ parameter is not used, it defaults to the language of the current node.
    def route_to(node, lang = @lang)
      my_url = Webgen::Path.url(@dest_path)
      pnode = node.proxy_node(lang)
      other_url = Webgen::Path.url(pnode['routing_path'] || pnode.dest_path)

      # resolve any '.' and '..' paths in the target url
      if other_url.path =~ /\/\.\.?\// && other_url.scheme == 'webgen'
        other_url = other_url.dup
        other_url.path = Pathname.new(other_url.path).cleanpath.to_s
      end
      route = my_url.route_to(other_url).to_s
      (route == '' ? File.basename(@dest_path) : route)
    end

    # Return the proxy node in language +lang+.
    #
    # This node should be used, for example, when routing to this node. The proxy node is found by
    # using the +proxy_path+ meta information. This meta information is usually set on directories
    # to specify the node that should be used for the "directory index".
    #
    # If the +lang+ parameter is not used, it defaults to the language of the current node.
    def proxy_node(lang = @lang)
      @meta_info['proxy_path'] && resolve(@meta_info['proxy_path'], lang, true) || self
    end

    # Return a HTML link from this node to the given node.
    #
    # If the +lang+ parameter is not used, it defaults to the language of the current node.
    #
    # You can optionally specify additional attributes for the HTML element in the +attr+ Hash.
    # Also, the meta information +link_attrs+ of the given +node+ is used, if available, to set
    # attributes. However, the +attr+ parameter takes precedence over the +link_attrs+ meta
    # information.
    #
    # If the special value 'link_text' is present in the attributes, it will be used as the link
    # text; otherwise the title of the +node+ will be used.
    def link_to(node, lang = @lang, attr = {})
      rnode = node.proxy_node(lang)
      attr = (rnode['link_attrs'].kind_of?(Hash) ? rnode['link_attrs'] : {}).merge(attr)
      link_text = attr.delete('link_text') || (rnode != node && rnode['routed_title']) || node['title']

      attr['href'] = route_to(node, lang)
      attr['hreflang'] = rnode.lang.to_s if rnode.lang
      attrs = attr.collect {|name,value| "#{name.to_s}=\"#{value}\"" }.sort.unshift('').join(' ')
      "<a#{attrs}>#{link_text}</a>"
    end

    # Return all versions of this node.
    def versions
      tree.node_access[:alcn].select {|alcn, n| n.node_info[:path] == node_info[:path]}.
        each_with_object({}) {|(_, v), h| h[v['version']] = v}
    end

  end

end
