 
 
 module.exports = Utils =
-  # Escape regular expression characters in a string
-  #
-  # Code from http://zetafleet.com/ via http://simonwillison.net/2006/Jan/20/escape/
-  regexpEscape: (string) ->
-    string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
+
+  # Code from <http://zetafleet.com/>
+  # via <http://blog.simonwillison.net/post/57956816139/escape>
+  regexpEscapePattern : /[-[\]{}()*+?.,\\^$|#\s]/g
+  regexpEscapeReplace : '\\$&'
+
+  # Escape regular expression characters in a String, an Array of Strings or
+  # any Object having a proper toString-method
+  regexpEscape: (obj) ->
+    if _.isArray obj
+      _.invoke(obj, 'replace', @regexpEscapePattern, @regexpEscapeReplace)
+    else if _.isString obj
+      obj.replace(@regexpEscapePattern, @regexpEscapeReplace)
+    else
+      @regexpEscape "#{obj}"
 
   # Detect and return the language that a given file is written in.
   #
-  # The language is also annotated with a name property, matching the laguages key in LANGUAGES.
+  # The language is also annotated with a name property, matching the language's
+  # key in LANGUAGES.
   getLanguage: (filePath, languageDefinitions = './languages') ->
     unless @_languageDetectionCache?
       @_languageDetectionCache = []

  splitSource: (data, language, options={}) ->
    lines = data.split /\r?\n/

    # Always strip shebangs - but don't shift it off the array to avoid the perf hit of walking the
    # array to update indices.
    lines[0] = '' if lines[0][0..1] == '#!'

    # Special case: If the language is comments-only, we can skip pygments
    return [new @Segment [], lines] if language.commentsOnly

    whitespaceMatch = if options.requireWhitespaceAfterToken then '\\s' else '\\s?'

    if language.singleLineComment?
      singleLines = @regexpEscape(language.singleLineComment).join '|'
      aSingleLine = ///^\s*(?:#{singleLines})(?:#{whitespaceMatch}(.*))?$///

    if language.multiLineComment?
      mlc = language.multiLineComment

      blockStarts = @regexpEscape(_.select mlc, (v, i) -> i % 3 == 0).join '|'
      blockLines  = @regexpEscape(_.select mlc, (v, i) -> i % 3 == 1).join '|'
      blockEnds   = @regexpEscape(_.select mlc, (v, i) -> i % 3 == 2).join '|'

      # No need to match for any particular real content in `aBlockStart`, as
      # either `aBlockLine`, `aBlockEnd` or the `isBlock` catch-all fallback
      # handles the real content, in the implementation below.
      aBlockStart = ///^\s*(?:#{blockStarts}(?:#{blockLines})?(?:#{whitespaceMatch}|$))///
      aBlockLine  = ///^\s*(?:#{blockLines}#{whitespaceMatch})(.*)$///
      aBlockEnd   = ///^\s*(?:#{blockLines}#{whitespaceMatch})?(.*)(?:#{blockEnds})$///
      aEmptyLine  = ///^\s*(?:#{blockLines})$///

    if language.ignorePrefix?
      {ignorePrefix} = language

    if language.foldPrefix?
      {foldPrefix} = language

    if (ignorePrefix? or foldPrefix?) and (singleLines? or blockStarts?)
      stripMarks = []
      stripMarks.push ignorePrefix if ignorePrefix?
      stripMarks.push foldPrefix if foldPrefix?
      stripMarks = @regexpEscape(stripMarks).join '|'

      # The dirty stuff happens here …
      singleStrip = ///
        (
          #{singleLines}                    # The comment marker(s) to keep …
          #{whitespaceMatch}                # … plus whitespace
        )
        (?:#{stripMarks})                   # The marker(s) to strip from result
      /// if singleLines?

      # … and here. 8-)
      blockStrip = ///
        (
          #{blockStarts}                    # The comment marker(s) to keep …
          (?:#{blockLines})?                # … optionally plus one more mark
          #{whitespaceMatch}                # … plus whitespace
        )
        (?:#{stripMarks})                   # The marker(s) to strip from result
      /// if blockStarts?


    isBlock   = false
    isFolded  = false
    isIgnored = false

    blockline = null
    indent    = 0

    for line in lines

      # Match that line to the language's block-comment syntax, if it exists
      if aBlockStart? and not isBlock and aBlockStart.test line

        isBlock = true

        if currSegment.code.length > 0
          segments.push currSegment
          currSegment   = new @Segment
          isFolded      = false

        # After stripping the block-comments start, preserving any inline stuff,
        # we continue at the `if isBlock` statement below.  We don't touch the
        # `line` itself, as we might need it.
        blockline = line.replace aBlockStart, ''

        if foldPrefix? and blockline.indexOf(foldPrefix) is 0
          ### ^ collapsing block-comments:
          # In block-comments only `aBlockStart` may initiate the collapsing.
          # This comment utilizes this syntax, by starting the comment with `^`.
          ###

          # Let's strip the “^” character from our documentation,
          # using the untouched original `line`
          blockline = line.replace blockStrip, '$1'
          isFolded  = true

        else if ignorePrefix? and blockline.indexOf(ignorePrefix) is 0
          ### } embedded block-comments:
          # In block-comments only `aBlockStart` may initiate the embedding.
          # This comment utilizes this syntax, by starting the comment with `}`.
          ###

          # Let's strip the “}” character from our documentation,
          # using the untouched original `line`
          blockline = line.replace blockStrip, '$1'
          isIgnored = true

      # This flag is triggered above.
      if isBlock

        # Catch all lines, unless there is a `blockline` from above.
        # These lines have all leading spaces stripped - this should be enhanced
        # to stripped only as many whitespaces as the initial `aBlockStart` has
        # as indent.
        # blockline = line.replace /^\s+/, '' unless blockline?
        blockline = line unless blockline?

        # Match a block-comment's end, even when `isFolded or isIgnored` flags
        # are true …
        if (match = blockline.match aBlockEnd)?
          ### … to ensure that we finally leave the block-comment, especially the ones on a single-line like this one. ###
          isBlock = false

          # Strip any `blockline`-prefixes or -suffixes when
          # `isFolded or isIgnored` flags are false.
          blockline = match[1] unless isFolded or isIgnored

        # Match a block-comment's line, when `isFolded or isIgnored` are false.
        else if not (isFolded or isIgnored) and (match = blockline.match aBlockLine)?
          # Strip any `blockline`-prefixes or -suffixes and continue below.
          blockline = match[1]

        if isIgnored
          currSegment.code.push blockline

          # Make sure that the next cycle starts fresh, 
          # if we are going to leave the block.
          isIgnored = false if not isBlock

        else
          # The previous cycle contained code, so lets start a new segment, but
          # only if the `isFolded` flag is false or if this is the first line to
          # fold (= foldMarker is empty).
          if currSegment.code.length > 0 and (not isFolded or currSegment.foldMarker is '')
            segments.push currSegment
            currSegment = new @Segment

          if isFolded

            # If the foldMarker is empty assign `blockline` to `foldMarker` …
            if currSegment.foldMarker is ''
              currSegment.foldMarker = blockline

            # … else collect the `blockline` as code.
            else
              currSegment.code.push blockline

          else

            ###
            # Implements the default handler for the `blockline` from above as
            # well as a catch-all fallback handling anything in a block-comment
            # that doesn't match anything above.
            #
            # So are in a multi-line block-comment, hence the whole line is part
            # of the comment.  This is especially needed for multi-line comment
            # contents starting immediately at the beginning of a line (without
            # indention, like those in CoffeeScripts).  The `aBlockLine` from
            # above can not catch those comments due to the `whitespaceMatch`
            # restrictions from above.  Empty block-comment lines, like the one
            # after this paragraph have no `whitespaceMatch` restriction …
            #
            # @description This comment is a block-comment with `@doctags`,
            #              indention and (needlessly) prefixed by `'#'`.
            # @description The very first comment in this file would render it's
            #              content as code if this `else`-clause is missing.
            ###
            if aEmptyLine.test line
              currSegment.comments.push ""

              ###
              Collect all but start- and end-block-comment lines, but include
              single-line block-comments that match `aBlockStart` and
              `aBlockEnd`, having the `isBlock` flag set to false at this point.
              ###
            else if (not isBlock and aBlockStart.test line) or not aBlockEnd.test line
              currSegment.comments.push blockline

        # Make sure the next cycle starts fresh.
        blockline = null

      # Match that line to the language's single line comment syntax.
      #
      # However, we treat all comments beginning with } as inline code commentary
      # and comments starting with ^ cause that comment and the following code
      # block to start folded.
      else if (match = line.match aSingleLine)?

        singleline = match[1]

        if singleline? and singleline isnt ''

          # } For example, this comment should be treated as part of our code.
          # } Achieved by prefixing the comment's content with “}”
          if ignorePrefix? and singleline.indexOf(ignorePrefix) is 0

            # } Hint: never start a new segment here, these comments are code !
            # } If we would do so the segments look visually not so appealing in
            # } the narrowed single-column-view.

            # Let's strip the “}” character from our documentation
            currSegment.code.push line.replace singleStrip, '$1'

          else

            # The previous cycle contained code, so lets start a new segment
            # and stop any folding.
            if currSegment.code.length > 0
              segments.push currSegment
              currSegment   = new @Segment
              isFolded      = false

            # It's always a good idea to put a comment before folded content
            # like this one here, because folded comments always have their
            # own code-segment in their current implementation (see above).
            # Without a leading comment, the folded code's segment would just
            # follow the above's code segment, which looks visually not so
            # appealing in the narrowed single-column-view.  
            #   
            # TODO: _Alternative (a)_: Improve folded comments to not start a new segment, like embedded comments from above. _(preferred solution)_    
            # TODO: _Alternative (b)_: Improve folded comments visual appearance in single-column view. _(easy solution)_  
            #
            # ^ … if we start this comment with “^” instead of “}” it and all
            # } code up to the next segment's first comment starts folded
            if foldPrefix? and singleline.indexOf(foldPrefix) is 0

              # } … so folding stops below, as this is a new segment !
              # Let's strip the “^” character from our documentation
              currSegment.foldMarker = line.replace singleStrip, '$1'

            else
              currSegment.comments.push singleline

      else
        currSegment.code.push line
