require 'volt/server/scope'
require 'volt/server/if_binding_setup'
require 'nokogiri'

# TODO: The section_name that we're passing in should probably be
# abstracted out.  Possibly this whole thing needs a rewrite.

class Template
	attr_accessor :current_scope, :section_name

	def initialize(template_parser, section_name, template, scope=Scope.new)
    @binding_number = 0

		@template_parser = template_parser
    @section_name = section_name
		@template = template
    @scopes = [scope]
    @current_scope = @scopes.first
	end

	def html
    if @template.respond_to?(:name) && @template.name[0] == ':'
      # Don't return the <:section> tags
      return @template.children.to_html
    else
      # if @template.class == Nokogiri::XML::NodeSet
      #   result = ''
      #   @template.each do |node|
      #     result << node.to_html
      #   end
      # else
    		result = @template.to_html
      # end
      result
    end
	end


	def add_binding(node, content)
		if content[0] == '/'
			add_close_mustache(node)
		elsif content[0] == '#'
			command, *content = content.split(/ /)
			content = content.join(' ')

			case command
		  when '#template'
		    return add_template(node, content)
			when '#each'
				return add_each_binding(node, content)
			when '#if'
				return add_if_binding(node, content)
      when '#elsif'
        return add_else_binding(node, content)
      when '#else'
        if content.present?
          # TODO: improve error, include line/file
          raise "#else should not include a condition, use #elsif instead.  #{content} was passed as a condition."
        end
        
        return add_else_binding(node, nil)
			else
				# TODO: Handle invalid command
				raise "Invalid Command"
			end
		else
			# text binding
			return add_text_binding(content)
		end
	end

	def add_template(node, content, name='Template')
		html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"

		@current_scope.add_binding(@binding_number, "lambda { |target, context, id| #{name}Binding.new(target, context, id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")

		@binding_number += 1
		return html
	end

	def add_each_binding(node, content)
		html = "<!-- $#{@binding_number} -->"
    
    content, variable_name = content.strip.split(/ as /)

    template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
		@current_scope.add_binding(@binding_number, "lambda { |target, context, id| EachBinding.new(target, context, id, Proc.new { #{content} }, #{variable_name.inspect}, #{template_name.inspect}) }")

		# Add the node, the binding number, then store the location where the
		# bindings for this block starts.
		@current_scope = Scope.new(@binding_number)
		@scopes << @current_scope

		@binding_number += 1
		return html
	end

	def add_if_binding(node, content)
		html = "<!-- $#{@binding_number} -->"

    template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
    if_binding_setup = IfBindingSetup.new
    if_binding_setup.add_branch(content, template_name)
    
		@current_scope.start_if_binding(@binding_number, if_binding_setup)

		# Add the node, the binding number, then store the location where the
		# bindings for this block starts.
		@current_scope = Scope.new(@binding_number)
		@scopes << @current_scope

		@binding_number += 1
		return html
	end
  
  def add_else_binding(node, content)
    html = add_close_mustache(node, false)
    
		html += "<!-- $#{@binding_number} -->"
    template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
    
    @current_scope.current_if_binding[1].add_branch(content, template_name)
    
		# Add the node, the binding number, then store the location where the
		# bindings for this block starts.
		@current_scope = Scope.new(@binding_number)
		@scopes << @current_scope

		@binding_number += 1
    
    return html
  end

	def add_close_mustache(node, close_if=true)
		scope = @scopes.pop
		@current_scope = @scopes.last
    
    # Close an outstanding if binding (if it exists)
    @current_scope.close_if_binding! if close_if
    
		# Track that this scope was closed out
		@current_scope.add_closed_child_scope(scope)

		html = "<!-- $/#{scope.outer_binding_number} -->"

		return html
	end

	# When we find a binding, we pass it's content in here and replace it with
	# the return value
	def add_text_binding(content)
		html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"

		@current_scope.add_binding(@binding_number, "lambda { |target, context, id| ContentBinding.new(target, context, id, Proc.new { #{content} }) }")

		@binding_number += 1
		return html
	end

  def setup_node_id(node)
    id = node['id']
		# First assign this node an id if it doesn't have one
		unless id
			id = node['id'] = "id#{@binding_number}"
			@binding_number += 1
		end
  end

  # Attribute bindings support multiple handlebar listeners
  # Exvoltle:
  #    <button click="{_primary} {_important}">...
  #
  # To accomplish this, we create a new listener from the existing ones in the Proc
  # that we pass to the binding when it is created.
	def add_attribute_binding(node, attribute, content)
		setup_node_id(node)
    
    if content =~ /^\{[^\{]+\}$/
      # Getter is the content inside of { ... }
      add_single_getter(node, attribute, content)
    else
      add_multiple_getters(node, attribute, content)
    end

	end
  
  def add_single_getter(node, attribute, content)
    if attribute == 'checked' || true
      # For a checkbox, we don't want to add
      getter = content[1..-2]
    else
      # Otherwise we should combine them
      # TODO: We should make .or handle assignment
      getter = "_tmp = #{content[1..-2]}.or('') ; _tmp.reactive_manager.setter! { |val| self.#{content[1..-2]} = val } ; _tmp"
    end
    
    @current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { #{getter} }) }")
  end
  
  def add_multiple_getters(node, attribute, content)
    case attribute
    when 'checked', 'value'
      if parts.size > 1
        # Multiple ReactiveValue's can not be passed to value or checked attributes.
        raise "Multiple bindings can not be passed to a #{attribute} binding."
      end
    end
    
    reactive_template_path = add_reactive_template(content)
    
    @current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { ReactiveTemplate.new(context, #{reactive_template_path.inspect}) }) }")
  end
  
  # Returns a path to a template for the content.  This can be passed
  # into ReactiveTemplate.new, along with the current context.
  def add_reactive_template(content)
    # Return a template path instead
    template_name = "__attribute/#{@binding_number}"
    full_template_path = "#{@template_parser.template_path}/#{section_name}/#{template_name}"
    @binding_number += 1
  
    attribute_template = Template.new(@template_parser, "#{section_name}/#{template_name}", Nokogiri::HTML::DocumentFragment.parse(content))
    @template_parser.add_template("#{section_name}/#{template_name}", attribute_template)
    attribute_template.start_walk
    attribute_template.pull_closed_block_scopes

    return full_template_path
  end

	def add_event_binding(node, attribute_name, content)
    setup_node_id(node)
    
    event = attribute_name[2..-1]
    
    if node.name == 'a'
      # For links, we need to add blank href to make it clickable.
      node['href'] ||= ''
    end

    @current_scope.add_binding(node['id'], "lambda { |target, context, id| EventBinding.new(target, context, id, #{event.inspect}, Proc.new {|event| #{content} })}")
  end

	def pull_closed_block_scopes(scope=@current_scope)
    if scope.closed_block_scopes
      scope.closed_block_scopes.each do |sub_scope|
        # Loop through any subscopes first, pull them in.
        pull_closed_block_scopes(sub_scope)
        
        # Grab everything between the start/end html comments
        start_node = find_by_comment("$#{sub_scope.outer_binding_number}")
        end_node = find_by_comment("$/#{sub_scope.outer_binding_number}")

        move_nodes_to_new_template(start_node, end_node, sub_scope)
      end
    end
  end


	def move_nodes_to_new_template(start_node, end_node, scope)
    # TODO: currently this doesn't handle spanning nodes within seperate containers.
    # so doing tr's doesn't work for some reason.
    
		start_parent = start_node.parent
		start_parent = start_parent.children if start_parent.is_a?(Nokogiri::HTML::DocumentFragment) || start_parent.is_a?(Nokogiri::XML::Element)
		start_index = start_parent.index(start_node) + 1

		end_parent = end_node.parent
		end_parent = end_parent.children if end_parent.is_a?(Nokogiri::HTML::DocumentFragment) || end_parent.is_a?(Nokogiri::XML::Element)
		end_index = end_parent.index(end_node) - 1

		move_nodes = start_parent[start_index..end_index]
		move_nodes.remove

    new_template = Template.new(@template_parser, section_name, move_nodes, scope)
    
    @template_parser.add_template("#{section_name}/__template/#{scope.outer_binding_number}", new_template)
	end


  def find_by_comment(name)
    return @template.xpath("descendant::comment()[. = ' #{name} ']").first
  end

	def start_walk
		walk(@template)
	end

	# We implement a dom walker that can walk down the dom and spit out output
	# html as we go
	def walk(node)
		case node.type
		when 1
			# html node
      walk_html_node(node)
		when 3
			# text node
      walk_text_node(node)
		end

		node.children.each do |child|
			walk(child)
		end
	end
  
  def walk_html_node(node)
    if node.name[0] == ':' && node.path.count('/') > 1
      parse_component(node)
    elsif node.name == 'textarea'
      parse_textarea(node)
    else
      parse_html_node(node)
    end
  end
  
  # We provide a quick way to render components with tags starting
  # with a :
  # Count the number of /'s in the path, if we are at the root node
  # we can ignore it, since this is the template its self.
  # TODO: Root node might not be the template if we parsed directly 
  # without a subtemplate specifier.  We need to find a good way to
  # parse only within the subtemplate.
  def parse_component(node)
    template_path = node.name[1..-1].gsub(':', '/')

    # Take the attributes and turn them into a hash
    attribute_hash = {}
    node.attribute_nodes.each do |attribute_node|
      content = attribute_node.value
      
      if !content.index('{')
        # passing in a string
        value = content.inspect
      elsif content =~ /^\{[^\}]+\}$/
        # Has one binding, just get it
        value = "Proc.new { #{content[1..-2]} }"
      else
        # Has multiple bindings, we need to render a template here
        attr_template_path = add_reactive_template(content)
        
        value = "Proc.new { ReactiveTemplate.new(context, #{attr_template_path.inspect}) }"
      end
      
      attribute_hash[attribute_node.name] = value
    end
    
    attributes_string = attribute_hash.to_a.map do |key, value|
      "#{key.inspect} => #{value}"
    end.join(', ')

    # Setup the arguments string, which goes to the TemplateBinding
    args_str = "#{template_path.inspect}"
    args_str << ", {#{attributes_string}}" if attribute_hash.size > 0

		new_html = add_template(node, args_str, 'Component')
    
    node.swap(new_html)#Nokogiri::HTML::DocumentFragment.parse(new_html))
  end
  
  def parse_textarea(node)
    # The contents of textareas should really be treated like a 
    # value= attribute.  So here we pull the content into a value attribute
    # if the textarea has bindings in the content.
    if node.inner_html =~ /\{[^\}]+\}/
      node[:value] = node.inner_html
      node.children.remove
    end
    
    parse_html_node(node)
  end
  
  def parse_html_node(node)
		node.attribute_nodes.each do |attribute_node|
		  if attribute_node.name =~ /^e\-/
        # We have an e- binding
        add_event_binding(node, attribute_node.name, attribute_node.value)

        # remove the attribute
        attribute_node.remove
			elsif attribute_node.value.match(/\{[^\}]+\}/)
        # Has bindings
				add_attribute_binding(node, attribute_node.name, attribute_node.value)

				# remove the attribute
				attribute_node.remove
			end
		end    
  end
  
  def walk_text_node(node)
		new_html = node.to_html.gsub(/\{([^\}]+)\}/) do |template_binding|
			add_binding(node, $1)
		end

    # puts "------! #{new_html.inspect} - #{node.class.inspect} - #{node.inspect}"

    # TODO: Broke here in jruby
    node.swap(new_html)# if new_html.blank?
    
    #Nokogiri::HTML::DocumentFragment.parse(new_html))
  end
end

class TemplateParser
	attr_accessor :dom, :bindings, :template_path

	def initialize(template, template_path)
		@templates = {}
    @template_path = template_path

    template_fragment = Nokogiri::HTML::DocumentFragment.parse(template)

    # Add templates for each section
    
    # Check for sections
    sections = []
    if template_fragment.children[0].name[0] == ':'
      template_fragment.children.each do |child|
        if child.is_a?(Nokogiri::XML::Element)
          sections << [child, child.name[1..-1]]
        end
      end
    else
      sections << [template_fragment, 'body']
    end
    
    sections.each do |section, name|
  		template = Template.new(self, name, section)
  		add_template(name, template)
  		template.start_walk
  		template.pull_closed_block_scopes
    end

	end

	def add_template(name, template)
    raise "Already defined at #{@template_path + '/' + name}" if @templates[@template_path + '/' + name]
		@templates[@template_path + '/' + name] = template
	end

	# Return the templates, but map the html from nokogiri to html
	def templates
		mapped = {}
		@templates.each_pair do |name, template|
			mapped[name] = {
				'html' => template.html,
				'bindings' => template.current_scope.bindings
			}
		end

		return mapped
	end

end