module RRTF
  class ListTable
    def initialize
      @templates = []
    end

    def new_template
      @templates.push ListTemplate.new(next_template_id)
      @templates.last
    end

    def to_rtf(indent=0)
      return '' if @templates.empty?

      prefix = indent > 0 ? ' ' * indent : ''

      # List table
      text = "#{prefix}{\\*\\listtable"
      @templates.each {|tpl| text << tpl.to_rtf}
      text << "}"

      # List override table, a Cargo Cult.
      text << "#{prefix}{\\*\\listoverridetable"
      @templates.each do |tpl|
        text << "{\\listoverride\\listid#{tpl.id}\\listoverridecount0\\ls#{tpl.id}}"
      end
      text << "}\n"
    end

    protected
      def next_template_id
        @templates.size + 1
      end

  end

  class ListMarker
    def initialize(name, codepoint=nil)
      @name      = name
      @codepoint = codepoint
    end

    def bullet?
      !@codepoint.nil?
    end

    def type
      bullet? ? :bullet : :decimal
    end

    def number_type
      # 23: bullet, 0: arabic
      # applies to the \levelnfcN macro
      #
      bullet? ? 23 : 0
    end

    def name
      name  = "\\{#@name\\}"
      name << '.' unless bullet?
      name
    end

    def template_format
      # The first char is the string size, the next ones are
      # either placeholders (\'0X) or actual characters to
      # include in the format. In the bullet case, \uc0 is
      # used to get rid of the multibyte translation: we want
      # an Unicode character.
      #
      # In the decimal case, we have a fixed format, with a
      # dot following the actual number.
      #
      if bullet?
        "\\'01\\uc0\\u#@codepoint"
      else
        "\\'02\\'00. "
      end
    end

    def text_format(n=nil)
      text =
        if bullet?
          "\\uc0\\u#@codepoint"
        else
          "#{n}."
        end

      "\t#{text}\t"
    end
  end

  class ListTemplate
    attr_reader :id

    Markers = {
      :disc    => ListMarker.new('disc',    0x2022),
      :hyphen  => ListMarker.new('hyphen',  0x2043),
      :decimal => ListMarker.new('decimal'        )
    }

    def initialize(id)
      @levels = []
      @id     = id
    end

    def level_for(level, kind = :bullets)
      @levels[level-1] ||= begin
        # Only disc for now: we'll add support
        # for more customization options later
        marker = Markers[kind == :bullets ? :disc : :decimal]
        ListLevel.new(self, marker, level)
      end
    end

    def to_rtf(indent=0)
      prefix = indent > 0 ? ' ' * indent : ''

      text = "#{prefix}{\\list\\listtemplate#{id}\\listhybrid"
      @levels.each {|lvl| text << lvl.to_rtf}
      text << "{\\listname;}\\listid#{id}}\n"

      text
    end
  end

  class ListLevel
    ValidLevels = (1..9)

    LevelTabs = [
      220,  720,  1133, 1700, 2267,
      2834, 3401, 3968, 4535, 5102,
      5669, 6236, 6803
    ].freeze

    ResetTabs = [560].concat(LevelTabs[2..-1]).freeze

    attr_reader :level, :marker

    def initialize(template, marker, level)
      unless marker.kind_of? ListMarker
        RTFError.fire("Invalid marker #{marker.inspect}")
      end

      unless ValidLevels.include? level
        RTFError.fire("Invalid list level: #{level}")
      end

      @template = template
      @level    = level
      @marker   = marker
    end

    def type
      @marker.type
    end

    def reset_tabs
      ResetTabs
    end

    def tabs
      @tabs ||= begin
        tabs = LevelTabs.dup # Kernel#tap would be prettier here

        (@level - 1).times do
          # Reverse-engineered while looking at Textedit.app
          # generated output: they already made sure that it
          # would look good on every RTF editor :-p
          #
          a,  = tabs.shift(3)
          a,b = a + 720, a + 1220
          tabs.shift while tabs.first < b
          tabs.unshift a, b
        end

        tabs
      end
    end

    def id
      @id ||= @template.id * 10 + level
    end

    def indent
      @indent ||= level * 720
    end

    def to_rtf(indent=0)
      prefix = indent > 0 ? ' ' * indent : ''

      text  = "#{prefix}{\\listlevel\\levelstartat1"

      # Marker type. The first declaration is for Backward Compatibility (BC).
      nfc  = @marker.number_type
      text << "\\levelnfc#{nfc}\\levelnfcn#{nfc}"

      # Justification, currently only left justified (0). First decl for BC.
      text << '\leveljc0\leveljcn0'

      # Character that follows the level text, currently only TAB.
      text << '\levelfollow0'

      # BC: Minimum distance from the left & right edges.
      text << '\levelindent0\levelspace360'

      # Marker name
      text << "{\\*\\levelmarker #{@marker.name}}"

      # Marker text format
      text << "{\\leveltext\\leveltemplateid#{id}#{@marker.template_format};}"
      text << '{\levelnumbers;}'

      # The actual spacing
      text << "\\fi-360\\li#{self.indent}\\lin#{self.indent}}\n"
    end

  end
end