<?php
/**
 * DokuWiki Plugin syntaxhighlighter4 (Syntax Component).
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  CrazyMax <contact@crazymax.dev>
 */

// must be run within Dokuwiki
if (!defined('DOKU_INC')) {
  die();
}

if (!defined('DOKU_LF')) {
  define('DOKU_LF', "\n");
}
if (!defined('DOKU_TAB')) {
  define('DOKU_TAB', "\t");
}
if (!defined('DOKU_PLUGIN')) {
  define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
}

require_once DOKU_PLUGIN.'syntax.php';

class syntax_plugin_syntaxhighlighter4 extends DokuWiki_Syntax_Plugin {

  protected $syntax;

  /**
   * @return string Syntax mode type
   */
  public function getType() {
      return 'protected';
  }

  /**
   * @return string Paragraph type
   */
  public function getPType() {
      return 'block';
  }

  /**
   * @return int Sort order - Low numbers go before high numbers
   */
  public function getSort() {
      return 195;
  }

  /**
   * Connect lookup pattern to lexer.
   *
   * @param string $mode Parser mode
   */
  public function connectTo($mode) {
      $this->Lexer->addEntryPattern('<sxh(?=[^\r\n]*?>.*?</sxh>)', $mode, 'plugin_syntaxhighlighter4');
      $tags = explode(',', $this->getConf('override'));
      foreach ($tags as $tag) {
        $this->Lexer->addEntryPattern('<' . $tag . '(?=[^\r\n]*?>.*?</' . $tag . '>)', $mode, 'plugin_syntaxhighlighter4');
      }
  }

  public function postConnect() {
      $this->Lexer->addExitPattern('</sxh>', 'plugin_syntaxhighlighter4');
      $tags = explode(',', $this->getConf('override'));
      foreach ($tags as $tag) {
        $this->Lexer->addExitPattern('</' . $tag . '>', 'plugin_syntaxhighlighter4');
      }
  }

  /**
   * Handle matches of the syntaxhighlighter4 syntax.
   *
   * @param string        $match   The match of the syntax
   * @param int           $state   The state of the handler
   * @param int           $pos   The position in the document
   * @param Doku_Handler  $handler The handler
   *
   * @return array Data for the renderer
   */
  public function handle($match, $state, $pos, Doku_Handler $handler) {
      switch ($state) {
        case DOKU_LEXER_ENTER:
          $this->syntax = strtolower(substr($match, 1));
          return false;
        case DOKU_LEXER_UNMATCHED:
          // will include everything from <sxh ... to ... </sxh>
          list($attr, $content) = preg_split('/>/u', $match, 2);
          if ($this->isSyntaxOk()) {
              $attr = trim($attr);
              if ($attr == null) {
                // No brush and no options, use "text" with default options.
                $attr = 'text';
              } elseif (substr($attr, 0, 1) == ';') {
                // Options but no brush, add "text" brush.
                $attr = 'text'.$attr;
              }
          } else {
              $attr = null;
          }
          return array($this->syntax, $attr, $content);
      }
      return false;
  }

  /**
   * Render xhtml output or metadata.
   *
   * @param string        $mode   Renderer mode (supported modes: xhtml)
   * @param Doku_Renderer $renderer The renderer
   * @param array         $data   The data from the handler() function
   *
   * @return bool If rendering was successful.
   */
  public function render($mode, Doku_Renderer $renderer, $data) {
      if ($mode != 'xhtml') {
        return false;
      }

      if (count($data) != 3) {
        return true;
      }

      list($this->syntax, $attr, $content) = $data;
      if ($this->isSyntaxOk()) {
        $title = $this->procTitle($attr);
        $highlight = $this->procHighlight($attr);
        $renderer->doc .= '<pre class="brush: '.strtolower($attr.$highlight).'"'.$title.'>'.$renderer->_xmlEntities($content).'</pre>';
      } else {
        $renderer->file($content);
      }

      return true;
  }

  private function procTitle($attr) {
      if ($this->syntax == 'file') {
        $title = trim(substr($attr, strpos($attr, ' ') + 1));
        if (!empty($title)) {
          return ' title="' . $title . '"';
        }
      } elseif (preg_match('/title:/i', $attr)) {
        // Extract title(s) from $attr string.
        $attr_array = explode(';', $attr);
        $title_array = preg_grep('/title:/i', $attr_array);
        // Extract everything BUT title(s) from $attr string.
        $not_title_array = preg_grep('/title:/i', $attr_array, PREG_GREP_INVERT);
        $attr = implode(';', $not_title_array);
        // If there are multiple titles, use the last one.
        $title = array_pop($title_array);
        return ' title="'.preg_replace("/.*title:\s{0,}(.*)/i", '$1', $title).'"';
      }

      return '';
  }

  private function procHighlight($attr) {
      $highlight = '';

      // Check highlight attr for lines ranges
      if (preg_match('/highlight:/i', $attr, $matches)) {
        // Extract highlight from $attr string.
        $attr_array = explode(';', $attr);
        $highlight_array = preg_grep('/highlight:/i', $attr_array);
        // Extract everything BUT highlight from $attr string.
        $not_highlight_array = preg_grep('/highlight:/i', $attr_array, PREG_GREP_INVERT);
        $attr = implode(';', $not_highlight_array);
        // If there are multiple hightlights, use the last one.
        $highlight_str = array_pop($highlight_array);
        $highlight_str = preg_replace("/.*highlight:\s{0,}(.*)/i", '$1', $highlight_str);
        // Remove [ ]
        $highlight_str = str_replace(array('[', ']'), '', $highlight_str);
        // Process ranges if exists
        $highlight_exp = explode(',', $highlight_str);
        foreach ($highlight_exp as $highlight_elt) {
          if (!empty($highlight)) {
              $highlight .= ',';
          }
          $highlight_elt = trim($highlight_elt);
          $highlight_elt_exp = explode('-', $highlight_elt);
          if (count($highlight_elt_exp) == 2) {
              foreach (range($highlight_elt_exp[0], $highlight_elt_exp[1]) as $key => $lineNumber) {
                if ($key > 0) {
                  $highlight .= ',';
                }
                $highlight .= $lineNumber;
              }
          } else {
              $highlight .= $highlight_elt;
          }
        }
        $highlight = ' highlight: ['.$highlight.']';
      }

      return $highlight;
  }

  private function isSyntaxOk() {
      if ($this->syntax == 'sxh') {
        return true;
      }
      $tags = explode(',', $this->getConf('override'));
      foreach ($tags as $tag) {
        if ($this->syntax == $tag) {
          return true;
        }
      }
      return false;
  }
}
