<?php
/**
 *  A simple tabbed area component, developed from the tutorial given here:
 *  http://net.tutsplus.com/tutorials/html-css-techniques/how-to-create-a-slick-tabbed-content-area/
 *
 *  Syntax:
 *    <TABAREA tabs="comma,separated,list">
 *      <TAB>Content of 'comma' including [[:syntax|wiki syntax]]</TAB>
 *      <TAB>Content of 'separated' and here **is** //some// ''formatting''</TAB>
 *      <TAB>Content of 'list'</TAB>
 *    </TABAREA>
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 */

if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');

/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_simpletabs
extends DokuWiki_Syntax_Plugin
{

  var $current_tab;
  var $tab_area_counter;
  var $tab_counter;
  var $tabs;


  function __construct()
  {
    global $PARSER_MODES;
    $this->allowedModes = array_merge($PARSER_MODES['container'],
                                      $PARSER_MODES['formatting'],
                                      $PARSER_MODES['substition'],
                                      $PARSER_MODES['protected'],
                                      $PARSER_MODES['disabled'],
                                      $PARSER_MODES['paragraphs']);
    $this->tab_area_counter = 0;
    $this->tab_counter = 0;
    $this->tabs = array();
  }

  /**
   * Get the type of syntax this plugin defines.
   */
  function getType()
  {
    return 'container';
  }


  /**
   * What kind of syntax do we allow (optional)
   */
  function getAllowedTypes()
  {
    return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
  }
  /** getAllowedTypes() **/

  /**
   * Define how this plugin is handled regarding paragraphs.
   *
   * <p>
   * This method is important for correct XHTML nesting. It returns
   * one of the following values:
   * </p>
   * <dl>
   * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
   * <dt>block</dt><dd>Open paragraphs need to be closed before
   * plugin output.</dd>
   * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
   * </dl>
   */
  //  function getPType()
  //  {
  //    return 'normal';
  //  }


  /**
   * Where to sort in?
   */
  function getSort()
  {
    return 45;
  }


   /**
    * Connect lookup pattern to lexer.
    */
  function connectTo($mode)
  {
    $this->Lexer->addEntryPattern('<TABAREA tabs=".*?">(?=.*?</TABAREA>)$',$mode,'plugin_simpletabs');
    $this->Lexer->addPattern('<TAB>', 'plugin_simpletabs');
    $this->Lexer->addPattern('</TAB>', 'plugin_simpletabs');
  }


  function postConnect()
  {
    $this->Lexer->addExitPattern('</TABAREA>','plugin_simpletabs');
  }


  /**
   * Handler to prepare matched data for the rendering process.
   *
   * <p>
   * The <tt>$aState</tt> parameter gives the type of pattern
   * which triggered the call to this method:
   * </p>
   * <dl>
   * <dt>DOKU_LEXER_ENTER</dt>
   * <dd>a pattern set by <tt>addEntryPattern()</tt></dd>
   * <dt>DOKU_LEXER_MATCHED</dt>
   * <dd>a pattern set by <tt>addPattern()</tt></dd>
   * <dt>DOKU_LEXER_EXIT</dt>
   * <dd> a pattern set by <tt>addExitPattern()</tt></dd>
   * <dt>DOKU_LEXER_SPECIAL</dt>
   * <dd>a pattern set by <tt>addSpecialPattern()</tt></dd>
   * <dt>DOKU_LEXER_UNMATCHED</dt>
   * <dd>ordinary text encountered within the plugin's syntax mode
   * which doesn't match any pattern.</dd>
   * </dl>
   * @param $aMatch String The text matched by the patterns.
   * @param $aState Integer The lexer state for the match.
   * @param $aPos Integer The character position of the matched text.
   * @param $aHandler Object Reference to the Doku_Handler object.
   * @return Integer The current lexer state for the match.
   * @public
   * @see render()
   * @static
   */
  function handle($match, $state, $pos, &$handler)
  {
    switch ($state)
    {
    case DOKU_LEXER_ENTER:
      $this->tab_area_counter++;
      //cho "<b>[DEBUG]</b> Encountered tabbed area: " . $this->tab_area_counter . "<br />";
      $this->current_tab = '';
      $this->tab_count = 0;
      $this->tab_names = array();
      if (preg_match('/tabs="(.+)"/', $match, $matches))
      {
        $this->tab_names = explode(',', $matches[1]);
      }
      return array($state, array($this->tab_area_counter, $this->tab_names, $this->getConf('defaulttab')));
      break;

    case DOKU_LEXER_MATCHED:
      $params = $match;
      if ($match == '</TAB>')
      {
        $this->current_tab = '';
	// close any section edit block
	$handler->_addCall('finishSectionEdit', array($pos - 1), $pos);
	// render end of tabbed area
        $params = array(true, $this->tab_area_counter, '', 0, false);
      }
      else
      {
        // Do we have a tab open?
        $close_first = false;
        if (!empty($this->current_tab))
        {
          // close any section edit block
          $handler->_addCall('finishSectionEdit', array($pos - 1), $pos);
          $close_first = true;
        }
        $this->current_tab = $this->tab_names[$this->tab_count];
        $this->tab_count++;
        $active = false;
        if ($this->current_tab == $this->getConf('defaulttab'))
        {
          $active = true;
        }
	// start section edit
        // - note the +5 is to account for the section starting "<TAB>"
        $handler->_addCall('startSectionEdit', array(($pos + 5), 'section', $this->current_tab), $pos);
        $params = array($close_first, $this->tab_area_counter, $this->current_tab, $this->tab_count, $active);
      }
      return array($state, $params);
      break;

    case DOKU_LEXER_UNMATCHED:
      if (!empty($this->current_tab))
      {
        // SPECIAL CASE: We have to handle headers ourselves (apparently -
        // having looked in several other plugins similar to this)
        $headerMatch = preg_grep('/([ \t]*={2,}[^\n]+={2,}[ \t]*(?=))/msSi', array($match));
        // - not a header, just pass it through
        if (empty($headerMatch))
        {
          //$handler->_addCall('cdata', array($match), $pos);
          return array($state, $match);
        }
        else
        {
          // if it's a == header ==, use the core header() renderer
          // (copied from core header() in inc/parser/handler.php)
          $title = trim($match);
          $level = 7 - strspn($title,'=');
          if ($level < 1)
          {
            $level = 1;
          }
          $title = trim($title,'=');
          $title = trim($title);

          $handler->_addCall('header',array($title,$level,$pos), $pos);
          // close the section edit the header could open
          /*
          if ($title && $level <= $conf['maxseclevel'])
          {
            $handler->addPluginCall('wrap_closesection', array(), DOKU_LEXER_SPECIAL, $pos, '');
          }
          */
        }
        return false;
      }
      break;

    case DOKU_LEXER_EXIT:
      $close_first = false;
      if (!empty($this->current_tab))
      {
	// close any pending section edit block
	$handler->_addCall('finishSectionEdit', array($pos - 1), $pos);

        $close_first = true;
      }
      // generate dummy heading at top level to ensure that no following headers are nested under titles in the tabbed area
      $handler->_addCall('header', array('#',1,$pos), $pos);
      return array($state, $close_first);
      break;

    case DOKU_LEXER_SPECIAL:
      break;
    }
    return array();
  }

  /**
   * Handle the actual output creation.
   *
   * <p>
   * The method checks for the given <tt>$aFormat</tt> and returns
   * <tt>FALSE</tt> when a format isn't supported. <tt>$aRenderer</tt>
   * contains a reference to the renderer object which is currently
   * handling the rendering. The contents of <tt>$aData</tt> is the
   * return value of the <tt>handle()</tt> method.
   * </p>
   * @see handle()
   */
  function render($mode, &$renderer, $data)
  {
    if($mode == 'xhtml')
    {
      list($state, $params) = $data;

      switch ($state)
      {
      case DOKU_LEXER_ENTER:
        list($tab_area_id, $tab_names, $active_tab) = $params;
	//cho "<b>[DEBUG]</b> Generating tabbed area: " . $tab_area_id . "<br />";
        $renderer->doc .= '
<!-- Tabbed Area - Begin -->
<a href="top' . $tab_area_id . '"></a>
<div class="tabbed_area" id="t' . $tab_area_id . '">
  <ul class="tabs">
';
        $total_tab_count = count($tab_names);
        $tab_count = 0;
        foreach ($tab_names as $tab_name)
        {
          $tab_count++;
          $renderer->doc .= '    <li><a href="javascript:tabSwitch(' . $tab_area_id . ',' . $total_tab_count . ',' . $tab_count . ');" id="t' . $tab_area_id . '_' . $tab_count . '"';
          if ($tab_name == $active_tab)
          {
            $renderer->doc .= ' class="active"';
          }
          $renderer->doc .= '>' . $tab_name . '</a></li>
';
        }
        $renderer->doc .= '  </ul>
';
        break;

      case DOKU_LEXER_MATCHED:
        list($close_tab, $tab_area_counter, $tab_name, $tab_content_count, $active) = $params;
        if ($close_tab)
        {
          $renderer->doc .= '<div style="clear:both;height:0px;"></div></div>
';
        }
        if (!empty($tab_name))
        {
          $this->tab_content_counter++;
          $renderer->doc .= '  <div id="tc' . $tab_area_counter . '_' . $tab_content_count . '" class="tab" ';
          if (!$active)
          {
            $renderer->doc .= ' style="display:none;"';
          }
          $renderer->doc .= '>';
        }
        break;

      case DOKU_LEXER_UNMATCHED:
        $renderer->doc .= $params;
        break;

      case DOKU_LEXER_EXIT:
        $renderer->doc .= '</div>
<!-- Tabbed Area - End -->';
        break;
      }
      return true;
    }
    return false;
  }
}

//Setup VIM: ex: et ts=4 enc=utf-8 :
?>