#
# Copyright (c) 2008-2017 Minero Aoki, Kenshi Muto
#               2002-2007 Minero Aoki
#
# This program is free software.
# You can distribute or modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the GNU LGPL, see the file "COPYING".
#

require 'review/preprocessor'
require 'review/book'
require 'review/textbuilder'

module ReVIEW


  class TOCParser

    def self.parse(chap)
      f = StringIO.new(chap.content, 'r:BOM|utf-8')
      stream = Preprocessor::Strip.new(f)
      parser = self.new
      return parser.parse(stream, chap).map {|root|
        root.number = chap.number
        root
      }
    end

    def self.chapter_node(chap)
      toc = self.parse(chap)
      if toc.size != 1
        $stderr.puts "warning: chapter #{toc.join} contains more than 1 chapter"
      end
      toc.first
    end

    def parse(stream, chap)
      f = stream
      roots = [] # list of chapters
      node_stack = []
      filename = chap.path
      while (line = f.gets)
        case line
        when /\A\#@\+\+\+$/
          beg = f.lineno
          line = f.gets() while line && line !~ /\A\#@---$/
          line  or
            error! filename, beg, 'unterminated list'
          next
        when /\A\#@/
          # do nothing
          next
        when /\A\s*\z/
          # do nothing
          next
        when /\A(={2,})[\[\s\{]/
          lev = $1.size
          lev <= 6  or
            error! filename, f.lineno, "section level too deep: #{lev}"
          label = get_label(line)
          if node_stack.empty?
            # missing chapter label
            dummy_chapter = Chapter.new(label, chap)
            node_stack.push dummy_chapter
            roots.push dummy_chapter
          end
          next if label.start_with?('[/') # ex) "[/column]"
          sec = Section.new(lev, label.gsub(/\A\{.*?\}\s?/, ''))
          node_stack.pop until node_stack.last.level < sec.level
          node_stack.last.add_child sec
          node_stack.push sec

        when /\A=[^=]/
          label = get_label(line)
          node_stack.clear
          new_chapter = Chapter.new(label, chap)
          node_stack.push new_chapter
          roots.push new_chapter

        when %r{\A//\w+(?:\[.*?\])*\{\s*\z}
          ! node_stack.empty?  or
            error! filename, f.lineno, 'list found before section label'
          node_stack.last.add_child(list = List.new)
          beg = f.lineno
          list.add line
          while (line = f.gets) && ! line.start_with?('//}')
            list.add line
          end
          line  or
            error! filename, beg, 'unterminated list'

        when /\A\/\/\w/
          # do nothing
          next
        else
          # if node_stack.empty?
          #   error! filename, f.lineno, 'text found before section label'
          # end
          next if node_stack.empty?
          node_stack.last.add_child(par = Paragraph.new(chap))
          par.add line
          while (line = f.gets) && line !~ /\A\s*\z/
            par.add line
          end
        end
      end

      roots
    end

    def get_label(line)
      line = line.strip.sub(/\A=+\s*/, '')
      compile_label(line)
    end

    def compile_label(line)
      b = ReVIEW::TEXTBuilder.new
      dummy_book = ReVIEW::Book::Base.load
      dummy_chapter = ReVIEW::Book::Chapter.new(dummy_book, 1, '-', nil, StringIO.new)
      dummy_loc = Location.new('', StringIO.new)
      b.bind(ReVIEW::Compiler.new(b), dummy_chapter, dummy_loc)
      b.compile_inline(line)
    end

    def error!(filename, lineno, msg)
      raise "#{filename}:#{lineno}: #{msg}"
    end

#    class Node
#      def initialize(children = [])
#        @children = children
#      end
#
#      attr_reader :children
#
#      def add_child(c)
#        @children.push c
#      end
#
#      def each_node(&block)
#        @children.each do |c|
#          yield c
#          c.each(&block)
#        end
#      end
#
#      def each_child(&block)
#        @children.each(&block)
#      end
#
#      def chapter?
#        false
#      end
#
#      def each_section(&block)
#        @children.each { |n| n.yield_section(&block) }
#      end
#
#      def each_section_with_index
#        i = 0
#        each_section do |n|
#          yield n, i
#          i += 1
#        end
#      end
#
#      def section_size
#        cnt = 0
#        @children.each { |n| n.yield_section { cnt += 1 } }
#        cnt
#      end
#    end
#
#    class Section < Node
#      def initialize(level, label, path = nil)
#        super()
#        @level = level
#        @label = label
#        @filename = path ? real_filename(path) : nil
#      end
#
#      def real_filename(path)
#        if FileTest.symlink?(path)
#          File.basename(File.readlink(path))
#        else
#          File.basename(path)
#        end
#      end
#      private :real_filename
#
#      attr_reader :level
#      attr_reader :label
#
#      def estimated_lines
#        @children.inject(0) { |sum, n| sum + n.estimated_lines }
#      end
#
#      def yield_section
#        yield self
#      end
#
#      def inspect
#        "#<#{self.class} level=#{@level} #{@label}>"
#      end
#    end
#
#    class Chapter < Section
#      def initialize(label, chap)
#        super 1, label, chap.path
#        @chapter = chap
#        @chapter_id = chap.id
#        @path = chap.path
#        @page_metric = chap.book.page_metric
#        @volume = nil
#        @number = nil
#      end
#
#      attr_accessor :number
#
#      def chapter?
#        true
#      end
#
#      attr_reader :chapter_id
#
#      def volume
#        return @volume if @volume
#        @volume = @chapter.volume
#        @volume.lines = estimated_lines
#        @volume
#      end
#
#      def inspect
#        "#<#{self.class} #{@filename}>"
#      end
#    end
#
#    class Paragraph < Node
#      def initialize(chap)
#        @bytes = 0
#        @page_metric = chap.book.page_metric
#      end
#
#      def inspect
#        "#<#{self.class}>"
#      end
#
#      def add(line)
#        @bytes += line.strip.bytesize
#      end
#
#      def estimated_lines
#        (@bytes + 2) / @page_metric.text.n_columns + 1
#      end
#
#      def yield_section
#      end
#    end
#
#    class List < Node
#      def initialize
#        @lines = 0
#      end
#
#      def inspect
#        "#<#{self.class}>"
#      end
#
#      def add(_line)
#        @lines += 1
#      end
#
#      def estimated_lines
#        @lines + 2
#      end
#
#      def yield_section
#      end
#    end

  end


end
