class String
  def diff_string; to_s; end
end

class Array
  def diff_string; to_s; end
end

module LaTeX

  module CompositeElement
    include Enumerable
    def to_s
      map { |a,b| a.to_s }.join
    end
  end

  class Environment
    include CompositeElement

    attr_reader :open, :args, :close, :content, :name

    def initialize(open_,args,content,close_,verbatim=false)
      start_name = open_[/\A\\begin\{([^\}]+)\}/,1]
      end_name = close_[/\A\\end\{([^\}]+)\}/,1]
      if start_name != end_name then
        raise Parser::Error, "Environment #{start_name} ended unexpectedly with #{close_}"
      end
      @name = start_name
      @open = open_
      @args = args
      @content = content
      @close = close_
      @verbatim = verbatim
    end

    def ==(other)
      Environment === other and @name == other.name and @content == other.content and @args == other.args
    end

    def ignore?
      @name == "DIFnomarkup" or @verbatim
    end

    def diff_string
      "\\begin{#{@name}}\\end{#{@name}}"
    end

    def each
      yield @open, :atomic
      @args.each { |a| yield a, :atomic }
      yield @content, @name == "DIFatomic" ? :recurse_add_del : :recurse
      yield @close, :atomic
    end
  end

  class StandalonePunctuation
    class << self
      alias :real_new :new
      def new(punct)
        (@instances ||= {})[punct] ||= self.real_new(punct)
      end
    end
    
    def initialize(punct)
      @punct = punct
    end
    
    def ==(other)
      StandalonePunctuation === other and to_s == other.to_s
    end
        
    def atomic?
      false
    end
    
    def diff_string; to_s; end
    def to_s
      @punct
    end
  end

  class Group
    include CompositeElement

    attr_reader :left, :content, :right

    def initialize(left,content,right)
      @left = left
      @right = right
      @content = content
    end

    def safe?
      false
    end

    def ==(other)
      Group === other and @left == other.left and @right == other.right and @content == other.content
    end

    def each
      yield @left, :atomic
      yield @content, :recurse
      yield @right, :atomic
    end

    def diff_string
      "#{@left}#{@right}"
    end
  end

  class Argument < Group; end

  def self.combine_patterns(pats)
    Regexp.new("\\A\\\\(?:" + pats.map { |p| Regexp === p ? p : Regexp.quote(p) }.join("|") + ")\\*?\\Z")
  end

  class ControlSequence
    include CompositeElement

    MATHS_PATTERNS = %w[ 
      alpha theta tau beta vartheta pi upsilon gamma 
      varpi phi delta kappa rho varphi epsilon lambda 
      varrho chi varepsilon mu sigma psi zeta nu varsigma
      omega eta xi Gamma Lambda Sigma Psi Delta Xi Upsilon 
      Omega Theta Pi Phi
    ]

    # These patterns match control sequences that take a curly
    # bracket argument containing text that should be considered
    # for diffing.
    # Array of patterns that gets compiled into a mammoth regular expression.
    def self.textual_patterns
      @textual_patterns ||= %w[
          chapter section subsection subsubsection code 
          textbf texttt textrm textsf paragraph 
          item caption emph fbox framebox makebox mbox 
        ] + [/text.*/]
    end
    
    # These patterns match control sequences that are considered safe
    # to appear inside blocks marked as being different. Otherwise,
    # the diff markers will be cancelled before the control sequence is output.
    # Array of patterns that gets compiled into a mammoth regular expression.
    def self.safe_patterns
      @safe_patterns ||= %w[
        secref chapref figref acro
        copyright fbox framebox hspace 
        makebox mbox pageref ref
        usebox pounds frac sqrt times bullet cite backslash
      ] + [/math.*/,/text.*/] + MATHS_PATTERNS
    end

    def self.textual_pattern
      @textual_pattern ||= LaTeX.combine_patterns(textual_patterns)
    end

    def self.safe_pattern
      @safe_pattern ||= LaTeX.combine_patterns(safe_patterns)
    end

    attr_reader :name, :args

    def initialize(name, cs_arg, args)
      @name = name
      @args = args
      if cs_arg
        @args.unshift [cs_arg]
      end
    end

    def textual?
      @name =~ self.class.textual_pattern
    end

    def safe?
      @name =~ self.class.safe_pattern
    end

    def atomic?
      safe?
    end

    def comment_if_deleted?
      not safe? and not textual?
    end 

    def deleted!
      case @name
      when /\A\\chapter|section|subsection|subsubsection\Z/
        @name += "*"
      end
    end

    def ==(other)
      ControlSequence === other and @name == other.name and @args == other.args
    end

    def diff_string
      if textual? then "#{@name}" else to_s end
    end

    def each
      yield @name, :atomic
      @args.each { |a| yield a, textual? ? :recurse : :atomic }
    end
  end

  module CachedElement
    def cache
      @cache ||= {}
    end
    def create(content)
      cache[content] ||= self.new(content)
    end
  end

  NONEMPTY_ARRAY = []
  def NONEMPTY_ARRAY.empty?; false; end

  class Word < String
    def to_yaml_properties
      NONEMPTY_ARRAY
    end
    
    class << self
      include CachedElement
    end
    def diff_string; to_s; end
  end

  class Whitespace < String
    class << self
      include CachedElement
    end

    def to_yaml_properties
      NONEMPTY_ARRAY
    end

    def to_s
      content
    end

    def ==(other)
      Whitespace === other
    end

    def content
      String.new(self)
    end
  end

  class ParagraphBreak < String
    def to_yaml_properties
      NONEMPTY_ARRAY
    end

    def ==(other)
      ParagraphBreak === other
    end

    def atomic?
      false
    end
  end
end

