require 'rdoc/markup/simple_markup/lines.rb'
#require 'rdoc/markup/simple_markup/to_flow.rb'

module SM

  ##
  # A Fragment is a chunk of text, subclassed as a paragraph, a list
  # entry, or verbatim text

  class Fragment
    attr_reader   :level, :param, :txt
    attr_accessor :type

    def initialize(level, param, type, txt)
      @level = level
      @param = param
      @type  = type
      @txt   = ""
      add_text(txt) if txt
    end

    def add_text(txt)
      @txt << " " if @txt.length > 0
      @txt << txt.tr_s("\n ", "  ").strip
    end

    def to_s
      "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
    end

    ######
    # This is a simple factory system that lets us associate fragement
    # types (a string) with a subclass of fragment

    TYPE_MAP = {}

    def Fragment.type_name(name)
      TYPE_MAP[name] = self
    end

    def Fragment.for(line)
      klass =  TYPE_MAP[line.type] ||
        raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
      return klass.new(line.level, line.param, line.flag, line.text)
    end
  end

  ##
  # A paragraph is a fragment which gets wrapped to fit. We remove all
  # newlines when we're created, and have them put back on output

  class Paragraph < Fragment
    type_name Line::PARAGRAPH
  end

  class BlankLine < Paragraph
    type_name Line::BLANK
  end

  class Heading < Paragraph
    type_name Line::HEADING

    def head_level
      @param.to_i
    end
  end

  ##
  # A List is a fragment with some kind of label
  #

  class ListBase < Paragraph
    # List types
    BULLET  = :BULLET
    NUMBER  = :NUMBER
    UPPERALPHA  = :UPPERALPHA
    LOWERALPHA  = :LOWERALPHA
    LABELED = :LABELED
    NOTE    = :NOTE
  end

  class ListItem < ListBase
    type_name Line::LIST

    #  def label
    #    am = AttributeManager.new(@param)
    #    am.flow
    #  end
  end

  class ListStart < ListBase
    def initialize(level, param, type)
      super(level, param, type, nil)
    end
  end

  class ListEnd < ListBase
    def initialize(level, type)
      super(level, "", type, nil)
    end
  end

  ##
  # Verbatim code contains lines that don't get wrapped.

  class Verbatim < Fragment
    type_name  Line::VERBATIM

    def add_text(txt)
      @txt << txt.chomp << "\n"
    end

  end

  ##
  # A horizontal rule
  class Rule < Fragment
    type_name Line::RULE
  end


  # Collect groups of lines together. Each group
  # will end up containing a flow of text

  class LineCollection
    
    def initialize
      @fragments = []
    end

    def add(fragment)
      @fragments << fragment
    end

    def each(&b)
      @fragments.each(&b)
    end

    # For testing
    def to_a
      @fragments.map {|fragment| fragment.to_s}
    end

    # Factory for different fragment types
    def fragment_for(*args)
      Fragment.for(*args)
    end

    # tidy up at the end
    def normalize
      change_verbatim_blank_lines
      add_list_start_and_ends
      add_list_breaks
      tidy_blank_lines
    end

    def to_s
      @fragments.join("\n----\n")
    end

    def accept(am, visitor)

      visitor.start_accepting

      @fragments.each do |fragment|
        case fragment
        when Verbatim
          visitor.accept_verbatim(am, fragment)
        when Rule
          visitor.accept_rule(am, fragment)
        when ListStart
          visitor.accept_list_start(am, fragment)
        when ListEnd
          visitor.accept_list_end(am, fragment)
        when ListItem
          visitor.accept_list_item(am, fragment)
        when BlankLine
          visitor.accept_blank_line(am, fragment)
        when Heading
          visitor.accept_heading(am, fragment)
        when Paragraph
          visitor.accept_paragraph(am, fragment)
        end
      end

      visitor.end_accepting
    end
    #######
    private
    #######

    # If you have:
    #
    #    normal paragraph text.
    #
    #       this is code
    #   
    #       and more code
    #
    # You'll end up with the fragments Paragraph, BlankLine, 
    # Verbatim, BlankLine, Verbatim, BlankLine, etc
    #
    # The BlankLine in the middle of the verbatim chunk needs to
    # be changed to a real verbatim newline, and the two
    # verbatim blocks merged
    #
    #    
    def change_verbatim_blank_lines
      frag_block = nil
      blank_count = 0
      @fragments.each_with_index do |frag, i|
        if frag_block.nil?
          frag_block = frag if Verbatim === frag
        else
          case frag
          when Verbatim
            blank_count.times { frag_block.add_text("\n") }
            blank_count = 0
            frag_block.add_text(frag.txt)
            @fragments[i] = nil    # remove out current fragment
          when BlankLine
            if frag_block
              blank_count += 1
              @fragments[i] = nil
            end
          else
            frag_block = nil
            blank_count = 0
          end
        end
      end
      @fragments.compact!
    end

    # List nesting is implicit given the level of
    # Make it explicit, just to make life a tad
    # easier for the output processors

    def add_list_start_and_ends
      level = 0
      res = []
      type_stack = []

      @fragments.each do |fragment|
        # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
        new_level = fragment.level
        while (level < new_level)
          level += 1
          type = fragment.type
          res << ListStart.new(level, fragment.param, type) if type
          type_stack.push type
          # $stderr.puts "Start: #{level}"
        end

        while level > new_level
          type = type_stack.pop
          res << ListEnd.new(level, type) if type
          level -= 1
          # $stderr.puts "End: #{level}, #{type}"
        end

        res << fragment
        level = fragment.level
      end
      level.downto(1) do |i|
        type = type_stack.pop
        res << ListEnd.new(i, type) if type
      end

      @fragments = res
    end

    # now insert start/ends between list entries at the
    # same level that have different element types

    def add_list_breaks
      res = @fragments

      @fragments = []
      list_stack = []

      res.each do |fragment|
        case fragment
        when ListStart
          list_stack.push fragment
        when ListEnd
          start = list_stack.pop
          fragment.type = start.type
        when ListItem
          l = list_stack.last
          if fragment.type != l.type
            @fragments << ListEnd.new(l.level, l.type)
            start = ListStart.new(l.level, fragment.param, fragment.type)
            @fragments << start
            list_stack.pop
            list_stack.push start
          end
        else
          ;
        end
        @fragments << fragment
      end
    end

    # Finally tidy up the blank lines:
    # * change Blank/ListEnd into ListEnd/Blank
    # * remove blank lines at the front

    def tidy_blank_lines
      (@fragments.size - 1).times do |i|
        if @fragments[i].kind_of?(BlankLine) and 
            @fragments[i+1].kind_of?(ListEnd)
          @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i] 
        end
      end

      # remove leading blanks
      @fragments.each_with_index do |f, i|
        break unless f.kind_of? BlankLine
        @fragments[i] = nil
      end

      @fragments.compact!
    end

  end
  
end
