<?php
/**
 * ## TbExtendedGridView class file
 *
 * @author Antonio Ramirez <antonio@clevertech.biz>
 * @copyright Copyright &copy; Clevertech 2012-
 * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php) 
 */

Yii::import('booster.widgets.TbGridView');

/**
 *## TbExtendedGridView is an extended version of TbGridView.
 *
 * Features are:
 *  - Display an extended summary of the records shown. The extended summary can be configured to any of the
 *  {@link TbOperation} type of widgets.
 *  - Automatic chart display (using TbHighCharts widget), where user can 'switch' between views.
 *  - Selectable cells
 *  - Sortable rows
 *
 * @property CActiveDataProvider $dataProvider the data provider for the view.
 * @property TbDataColumn[] $columns
 *
 * @package booster.widgets.grids
 */
class TbExtendedGridView extends TbGridView {
	
	/**
	 * @var bool $fixedHeader if set to true will keep the header fixed  position
	 */
	public $fixedHeader = false;

	/**
	 * @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position
	 * at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended
	 * to use $headerOffset=40;
	 */
	public $headerOffset = 0;

	/**
	 * @var string the template to be used to control the layout of various sections in the view.
	 * These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the
	 * extended summary, summary text, the items, and the pager.
	 */
	public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}";

	/**
	 * @var array $extendedSummary displays an extended summary version.
	 * There are different types of summary types,
	 * please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation}
	 * {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}.
	 *
	 * The following is an example, please review the different types of TbOperation classes to find out more about
	 * its configuration parameters.
	 *
	 * <pre>
	 *  'extendedSummary' => array(
	 *      'title' => '',      // the extended summary title
	 *      'columns' => array( // the 'columns' that will be displayed at the extended summary
	 *          'id' => array(  // column name "id"
	 *              'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display
	 *              'label' => 'Sum of Ids'     // label is name of label of the resulted value (ie Sum of Ids:)
	 *          ),
	 *          'results' => array(   // column name "results"
	 *              'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation
	 *              'label' => 'How Many Of Each? ', // the label of the operation
	 *              'types' => array(               // TbPercentOfTypeGooglePieOperation "types" attributes
	 *                  '0' => array('label' => 'zeros'),   // a value of "0" will be labelled "zeros"
	 *                  '1' => array('label' => 'ones'),    // a value of "1" will be labelled "ones"
	 *                  '2' => array('label' => 'twos'))    // a value of "2" will be labelled "twos"
	 *          )
	 *      )
	 * ),
	 * </pre>
	 */
	public $extendedSummary = array();

	/**
	 * @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary
	 */
	public $extendedSummaryCssClass = 'extended-summary';

	/**
	 * @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary
	 */
	public $extendedSummaryOptions = array();

	/**
	 * @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated.
	 * It is used internally to render scripts required for components to work correctly.  You may use it for your own
	 * scripts, just make sure it is of type array.
	 */
	public $componentsAfterAjaxUpdate = array();

	/**
	 * @var array $componentsReadyScripts hold scripts that will be executed on document ready.
	 * It is used internally to render scripts required for components to work correctly. You may use it for your own
	 * scripts, just make sure it is of type array.
	 */
	public $componentsReadyScripts = array();

	/**
	 * @var array $chartOptions if configured, the extended view will display a highcharts chart.
	 */
	public $chartOptions = array();

	/**
	 * @var bool $sortableRows. If true the rows at the table will be sortable.
	 */
	public $sortableRows = false;

	/**
	 * @var string Database field name for row sorting
	 */
	public $sortableAttribute = 'sort_order';

	/**
	 * @var boolean Save sort order by ajax defaults to false
	 * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
	 */
	public $sortableAjaxSave = false;

	/**
	 * @var string Name of the action to call and sort values
	 * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
	 *
	 * <pre>
	 *  'sortableAction' => 'module/controller/sortable' | 'controller/sortable'
	 * </pre>
	 *
	 * The widget will make use of the string to create the URL and then append $sortableAttribute
	 * @see $sortableAttribute
	 */
	public $sortableAction;

	/**
	 * @var string a javascript function that will be invoked after a successful sorting is done.
	 * The function signature is <code>function(id, position)</code> where 'id' refers to the ID of the model id key,
	 * 'position' the new position in the list.
	 */
	public $afterSortableUpdate;

	/**
	 * @var bool whether to allow selecting of cells
	 */
	public $selectableCells = false;

	/**
	 * @var string the filter to use to allow selection. For example, if you set the "htmlOptions" property of a column to have a
	 * "class" of "tobeselected", you could set this property as: "td.tobeselected" in order to allow  selection to
	 * those columns with that class only.
	 */
	public $selectableCellsFilter = 'td';

	/**
	 * @var string a javascript function that will be invoked after a selection is done.
	 * The function signature is <code>function(selected)</code> where 'selected' refers to the selected columns.
	 */
	public $afterSelectableCells;
	/**
	 * @var array the configuration options to display a TbBulkActions widget
	 * @see TbBulkActions widget for its configuration
	 */
	public $bulkActions = array();

	/**
	 * @var string the aligment of the bulk actions. It can be 'left' or 'right'.
	 */
	public $bulkActionAlign = 'right';

	/**
	 * @var TbBulkActions component that will display the bulk actions to the grid
	 */
	protected $bulk;

	/**
	 * @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the
	 * extended summary
	 */
	protected $displayExtendedSummary;
	/**
	 * @var boolean $displayChart a helper property that is set to true if we have to render a chart.
	 */
	protected $displayChart;

	/**
	 * @var TbOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values.
	 */
	protected $extendedSummaryTypes = array();

	/**
	 * @var array $extendedSummaryOperations hold the supported operation types
	 */
	protected $extendedSummaryOperations = array(
		'TbSumOperation',
		'TbCountOfTypeOperation',
		'TbPercentOfTypeOperation',
		'TbPercentOfTypeEasyPieOperation',
		'TbPercentOfTypeGooglePieOperation'
	);

	/**
	 *### .init()
	 *
	 * Widget initialization
	 */
	public function init(){

		if ($this->shouldEnableExtendedSummary())
			$this->prepareDisplayingExtendedSummary();

		if ($this->shouldEnableChart() && $this->hasData())
			$this->enableChart();

		if ($this->shouldEnableBulkActions())
			$this->enableBulkActions();

		$this->fillSelectionChangedProperty();

		parent::init();
	}

	/**
	 *### .renderContent()
	 *
	 * Renders grid content
	 */
	public function renderContent()
	{
		parent::renderContent();
		$this->registerCustomClientScript();
	}

	/**
	 *### .renderKeys()
	 *
	 * Renders the key values of the data in a hidden tag.
	 */
	public function renderKeys()
	{
		$data = $this->dataProvider->getData();
		
		if (!$this->sortableRows || (isset($data[0]) && !isset($data[0]->attributes[(string)$this->sortableAttribute]))) {
			parent::renderKeys();
		}

		echo CHtml::openTag(
			'div',
			array(
				'class' => 'keys',
				'style' => 'display:none',
				'title' => Yii::app()->getRequest()->getUrl(),
			)
		);
		foreach ($data as $d) {
			echo CHtml::tag(
				'span',
				array('data-order' => $this->getAttribute($d, $this->sortableAttribute)),
				CHtml::encode($this->getPrimaryKey($d))
			);
		}
		echo "</div>\n";
		return true;
	}

	/**
	 *### .getAttribute()
	 *
	 * Helper function to get an attribute from the data
	 *
	 * @param CActiveRecord $data
	 * @param string $attribute the attribute to get
	 *
	 * @return mixed the attribute value null if none found
	 */
	protected function getAttribute($data, $attribute)
	{
		if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) {
			return $data->{$attribute};
		}

		if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
			if (is_object($data) && isset($data->{$attribute})) {
				return $data->{$attribute};
			}
			if (isset($data[$attribute])) {
				return $data[$attribute];
			}
		}
		return null;
	}

	/**
	 *### .getPrimaryKey()
	 *
	 * Helper function to return the primary key of the $data
	 * IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma
	 *
	 * @param CActiveRecord $data
	 *
	 * @return null|string
	 */
	protected function getPrimaryKey($data)
	{
		if ($this->dataProvider instanceof CActiveDataProvider) {
			$key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->dataProvider->keyAttribute};
			return is_array($key) ? implode(',', $key) : $key;
		}
		if (($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) && !empty($this->dataProvider->keyField)) {
			return is_object($data) ? $data->{$this->dataProvider->keyField}
				: $data[$this->dataProvider->keyField];
		}

		return null;
	}

	/**
	 *### .renderTableHeader()
	 *
	 * Renders grid header
	 */
	public function renderTableHeader() {
		
		$this->renderChart();
		parent::renderTableHeader();
	}

	/**
	 *### .renderTableFooter()
	 *
	 * Renders the table footer.
	 */
	public function renderTableFooter()
	{
		$hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER;

		$hasFooter = $this->getHasFooter();
		if ($this->bulk !== null || $hasFilter || $hasFooter) {
			echo "<tfoot>\n";
			if ($hasFooter) {
				echo "<tr>\n";
				/** @var $column CDataColumn */
				foreach ($this->columns as $column) {
					$column->renderFooterCell();
				}
				echo "</tr>\n";
			}
			if ($hasFilter) {
				$this->renderFilter();
			}

			if ($this->bulk !== null) {
				$this->renderBulkActions();
			}
			echo "</tfoot>\n";
		}
	}

	/**
	 *### .renderBulkActions()
	 */
	public function renderBulkActions() {
		
        Booster::getBooster()->registerAssetJs('jquery.saveselection.gridview.js');
        $this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.afterUpdateGrid('".$this->id."');";
		echo '<tr><td colspan="' . count($this->columns) . '">';
		$this->bulk->renderButtons();
		echo '</td></tr>';
	}


	/**
	 *### .renderChart()
	 *
	 * Renders chart
	 * @throws CException
	 */
	public function renderChart() {
		
		if (!$this->displayChart || $this->dataProvider->getItemCount() <= 0) {
			return;
		}

		if (!isset($this->chartOptions['data']['series'])) {
			throw new CException(Yii::t(
				'zii',
				'You need to set the "series" attribute in order to render a chart'
			));
		}

		$configSeries = $this->chartOptions['data']['series'];
		if (!is_array($configSeries)) {
			throw new CException(Yii::t('zii', '"chartOptions.series" is expected to be an array.'));
		}


		if (!isset($this->chartOptions['config'])) {
			$this->chartOptions['config'] = array();
		}

		// ****************************************
		// render switch buttons
		$buttons = Yii::createComponent(
			array(
				'class' => 'booster.widgets.TbButtonGroup',
				'toggle' => 'radio',
				'buttons' => array(
					array(
						'label' => Yii::t('zii', 'Grid'),
						'url' => '#',
						'htmlOptions' => array('class' => 'active ' . $this->getId() . '-grid-control grid')
					),
					array(
						'label' => Yii::t('zii', 'Chart'),
						'url' => '#',
						'htmlOptions' => array('class' => $this->getId() . '-grid-control chart')
					),
				),
				'htmlOptions' => array('style' => 'margin-bottom:5px')
			)
		);
		echo '<div>';
		$buttons->init();
		$buttons->run();
		echo '</div>';

		$chartId = preg_replace('[-\\ ?]', '_', 'exgvwChart' . $this->getId()); // cleaning out most possible characters invalid as javascript variable identifiers.

		$this->componentsReadyScripts[] = '$(document).on("click",".' . $this->getId() . '-grid-control", function() {
			$(this).parent().find("input[type=\"radio\"]").parent().toggleClass("active");
			if ($(this).hasClass("grid") && $("#' . $this->getId() . ' #' . $chartId . '").is(":visible"))
			{
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
				$("#' . $this->getId() . ' table.items").show();
			}
			if ($(this).hasClass("chart") && $("#' . $this->getId() . ' table.items").is(":visible"))
			{
				$("#' . $this->getId() . ' table.items").hide();
				$("#' . $this->getId() . ' #' . $chartId . '").show();
			}
			return false;
		});';
		
		$this->componentsAfterAjaxUpdate[] = '
			if($("label.grid.'.$this->getId().'-grid-control").hasClass("active")) {
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
				$("#' . $this->getId() . ' table.items").show();
			} else {
				$("#' . $this->getId() . ' table.items").hide();
				$("#' . $this->getId() . ' #' . $chartId . '").show();
			}
		';
		// end switch buttons
		// ****************************************

		// render Chart
		// chart options
		$data = $this->dataProvider->getData();
		$count = count($data);
		$seriesData = array();
		$cnt = 0;
		foreach ($configSeries as $set) {
			$seriesData[$cnt] = array('name' => isset($set['name']) ? $set['name'] : null, 'data' => array());

			for ($row = 0; $row < $count; ++$row) {
				$column = $this->getColumnByName($set['attribute']);
				if (!is_null($column) && $column->value !== null) {
					$seriesData[$cnt]['data'][] = $this->evaluateExpression(
						$column->value,
						array('data' => $data[$row], 'row' => $row)
					);
				} else {
					$value = CHtml::value($data[$row], $set['attribute']);
					$seriesData[$cnt]['data'][] = is_numeric($value) ? (float)$value : $value;
				}

			}
			++$cnt;
		}

		$xAxisData = array();
		
		$xAxisData[] = array('categories'=>array());
		if(!empty($this->chartOptions['data']['xAxis'])){
			$xAxis = $this->chartOptions['data']['xAxis'];
			$categories = $xAxis['categories'];
			if(is_array($categories)) {
				$xAxisData['categories'] = $categories;
			} else { // field name
				for ($row = 0; $row < $count; ++$row) {
					$column = $this->getColumnByName($categories);
					if (!is_null($column) && $column->value !== null) {
						$xAxisData['categories'][] = $this->evaluateExpression(
								$column->value,
								array('data' => $data[$row], 'row' => $row)
						);
					} else {
						$value = CHtml::value($data[$row], $categories);
						$xAxisData['categories'][] = $value;
					}
				}
			}
		}
		
		// ****************************************
		// render chart
		$options = CMap::mergeArray(
			$this->chartOptions['config'],
			array('series' => $seriesData, 'xAxis' => $xAxisData)
		);
		$this->chartOptions['htmlOptions'] = isset($this->chartOptions['htmlOptions'])
			? $this->chartOptions['htmlOptions'] : array();
		
		// sorry but use a class to provide styles, we need this
		if(empty($this->chartOptions['htmlOptions']['style']))
			$this->chartOptions['htmlOptions']['style'] = 'width: 100%; height: 100%;';
		else
			$this->chartOptions['htmlOptions']['style'] = $this->chartOptions['htmlOptions']['style'].'; width: 100%; height: 100%;';
		
		// build unique ID
		// important!
		echo '<div>';
		if ($this->ajaxUpdate !== false) {
			if (isset($options['chart']) && is_array($options['chart'])) {
				$options['chart']['renderTo'] = $chartId;
			} else {
				$options['chart'] = array('renderTo' => $chartId);
			}
			$jsOptions = CJSON::encode($options);

			if (isset($this->chartOptions['htmlOptions']['data-config'])) {
				unset($this->chartOptions['htmlOptions']['data-config']);
			}

			echo "<div id='{$chartId}' " . CHtml::renderAttributes(
				$this->chartOptions['htmlOptions']
			) . " data-config='{$jsOptions}'></div>";
			
			/* fix for chart dimensions changing after ajax */
			$this->componentsAfterAjaxUpdate[] = "
				$('#".$chartId."').width($('#".$this->id." table').width());
				$('#".$chartId."').height($('#".$this->id." table').height() + 150);
				highchart{$chartId} = new Highcharts.Chart($('#{$chartId}').data('config'));
			";
		}
		$configChart = array(
			'class' => 'booster.widgets.TbHighCharts',
			'id' => $chartId,
			'options' => $options,
			'htmlOptions' => $this->chartOptions['htmlOptions']
		);
		$chart = Yii::createComponent($configChart);
		$chart->init();
		$chart->run();
		echo '</div>';
		// end chart display
		// ****************************************
		
		// check if the chart should appear by default
		if(isset($this->chartOptions['defaultView']) && $this->chartOptions['defaultView'] === true) {
			$this->componentsReadyScripts[] = '
				$(".' . $this->getId() . '-grid-control.grid").removeClass("active");
				$(".' . $this->getId() . '-grid-control.chart").addClass("active");
				$("#' . $this->getId() . ' table.items").hide();
				$("#' . $this->getId() . ' #' . $chartId . '").show();
			';
		} else {
			$this->componentsReadyScripts[] = '
				$(".' . $this->getId() . '-grid-control.grid").addClass("active");
				$(".' . $this->getId() . '-grid-control.chart").removeClass("active");
				$("#' . $this->getId() . ' table.items").show();
				$("#' . $this->getId() . ' #' . $chartId . '").hide();
			';
		}
	}

	/**
	 *### .renderTableRow()
	 *
	 * Renders a table body row.
	 *
	 * This method is a copy-paste from CGridView.renderTableRow(),
	 * because we cannot override *just* the `renderDataCell` routine (it's polymorphic)
	 * and Yii doesn't have a seam in place for us to do this.
	 * @see https://github.com/yiisoft/yii/pull/3571
	 *
	 * Only meaningful change in here is the replacement of the `$column->renderDataCell($row)`
	 * with our own method.
	 *
	 * @param integer $row the row number (zero-based).
	 *
	 * @deprecated This method will be removed after Yii 1.1.17 release.
	 */
	public function renderTableRow($row)
	{
		$htmlOptions = array();
		if ($this->rowHtmlOptionsExpression !== null) {
			$data = $this->dataProvider->data[$row];
			$options = $this->evaluateExpression(
				$this->rowHtmlOptionsExpression,
				array('row' => $row, 'data' => $data)
			);
			if (is_array($options)) {
				$htmlOptions = $options;
			}
		}

		if ($this->rowCssClassExpression !== null) {
			$data = $this->dataProvider->data[$row];
			$class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data));
		} elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
			$class = $this->rowCssClass[$row % $n];
		}

		if (!empty($class)) {
			if (isset($htmlOptions['class'])) {
				$htmlOptions['class'] .= ' ' . $class;
			} else {
				$htmlOptions['class'] = $class;
			}
		}

		echo CHtml::openTag('tr', $htmlOptions);
		foreach ($this->columns as $column)
			$this->renderDataCellProcessingSummariesIfNeeded($column, $row);
		echo CHtml::closeTag('tr');
	}

	/**
	 *### .renderExtendedSummary()
	 *
	 * Renders summary
	 */
	public function renderExtendedSummary()
	{
		if (!isset($this->extendedSummaryOptions['class'])) {
			$this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass;
		} else {
			$this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass;
		}

		echo '<div ' . CHtml::renderAttributes($this->extendedSummaryOptions) . '></div>';
	}

	/**
	 *### .renderExtendedSummaryContent()
	 *
	 * Renders summary content. Will be appended to
	 */
	public function renderExtendedSummaryContent()
	{
		if ($this->dataProvider->getItemCount() <= 0)
			return;

		if (empty($this->extendedSummaryTypes))
			return;

		echo '<div id="' . $this->id . '-extended-summary" style="display:none">';
		if (isset($this->extendedSummary['title'])) {
			echo '<h3>' . $this->extendedSummary['title'] . '</h3>';
		}
		foreach ($this->extendedSummaryTypes as $summaryType) {
			/** @var $summaryType TbOperation */
			$summaryType->run();
			echo '<br/>';
		}
		echo '</div>';
	}

	/**
	 *### .registerCustomClientScript()
	 *
	 * This script must be run at the end of content rendering not at the beginning as it is common with normal CGridViews
	 */
	public function registerCustomClientScript()
	{
		/** @var $cs CClientScript */
		$cs = Yii::app()->getClientScript();

		$fixedHeaderJs = '';
		if ($this->fixedHeader) {
            Booster::getBooster()->registerAssetJs('jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js');
			$fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});";
			$this->componentsAfterAjaxUpdate[] = $fixedHeaderJs;
		}

		if ($this->sortableRows) {
			$afterSortableUpdate = '';
			if ($this->afterSortableUpdate !== null) {
				if (!($this->afterSortableUpdate instanceof CJavaScriptExpression) && strpos(
					$this->afterSortableUpdate,
					'js:'
				) !== 0
				) {
					$afterSortableUpdate = new CJavaScriptExpression($this->afterSortableUpdate);
				} else {
					$afterSortableUpdate = $this->afterSortableUpdate;
				}
			}

			$this->selectableRows = 1;
			$cs->registerCoreScript('jquery.ui');
            Booster::getBooster()->registerAssetJs('jquery.sortable.gridview.js');

			if ($this->sortableAjaxSave && $this->sortableAction !== null) {
				$sortableAction = Yii::app()->createUrl(
					$this->sortableAction,
					array('sortableAttribute' => $this->sortableAttribute)
				);
			} else {
				$sortableAction = '';
			}

			$afterSortableUpdate = CJavaScript::encode($afterSortableUpdate);
			if (Yii::app()->request->enableCsrfValidation)
			{
				$csrfTokenName = Yii::app()->request->csrfTokenName;
				$csrfToken = Yii::app()->request->csrfToken;
				$csrf = "{'$csrfTokenName':'$csrfToken' }";
			} else
				$csrf = '{}';

			$this->componentsReadyScripts[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);";
			$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);";
		}

		if ($this->selectableCells) {
			$afterSelectableCells = '';
			if ($this->afterSelectableCells !== null) {
				if (!($this->afterSelectableCells instanceof CJavaScriptExpression) && strpos($this->afterSelectableCells,'js:') !== 0) {
					$afterSelectableCells = new CJavaScriptExpression($this->afterSelectableCells);
				} else {
					$afterSelectableCells = $this->afterSelectableCells;
				}
			}
			$cs->registerCoreScript('jquery.ui');
            Booster::getBooster()->registerAssetJs('jquery.selectable.gridview.js');
			$afterSelectableCells = CJavaScript::encode($afterSelectableCells);
			$this->componentsReadyScripts[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}',{$afterSelectableCells});";
			$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}', {$afterSelectableCells});";
		}

		$cs->registerScript(
			__CLASS__ . '#' . $this->id . 'Ex',
			'
			var $grid = $("#' . $this->id . '");
			' . $fixedHeaderJs . '
			if ($(".' . $this->extendedSummaryCssClass . '", $grid).length)
			{
				$(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html());
			}
			' . (count($this->componentsReadyScripts) ? implode(PHP_EOL, $this->componentsReadyScripts) : '') . '
			$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
				var qs = $.deparam.querystring(options.url);
				if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '")
				{
				    if (typeof (options.realsuccess) == "undefined" || options.realsuccess !== options.success)
				    {
                        options.realsuccess = options.success;
                        options.success = function(data)
                        {
                            if (options.realsuccess) {
                                options.realsuccess(data);
                                var $data = $("<div>" + data + "</div>");
                                // we need to get the grid again... as it has been updated
                                if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")))
                                {
                                    $(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")).html($("#' . $this->id . '-extended-summary", $data).html());
                                }
                                ' . (count($this->componentsAfterAjaxUpdate) ? implode(
                    PHP_EOL,
                    $this->componentsAfterAjaxUpdate
                ) : '') . '
                            }
                        }
				    }
				}
			});'
		);
	}

	/**
	 *### .parseColumnValue()
	 *
	 * @param CGridColumn $column
	 * @param string $value Value of the $column rendered by $column->renderDataCell($row).
	 */
	protected function processColumnValue($column, $value)
	{
		if (!($column instanceof CDataColumn))
			return;

		if (!array_key_exists($column->name, $this->extendedSummary['columns']))
			return;

		$config = $this->extendedSummary['columns'][$column->name];
		$config['column'] = $column;

		$this->getSummaryOperationInstance($column->name, $config)
			->processValue($value);
	}

	/**
	 *### .getSummaryOperationInstance()
	 *
	 * Each type of 'extended' summary
	 *
	 * @param string $name the name of the column
	 * @param array $config the configuration of the column at the extendedSummary
	 *
	 * @return mixed
	 * @throws CException
	 */
	protected function getSummaryOperationInstance($name, $config)
	{
		if (!isset($config['class'])) {
			throw new CException(Yii::t(
				'zii',
				'Column summary configuration must be an array containing a "type" element.'
			));
		}

		if (!in_array($config['class'], $this->extendedSummaryOperations)) {
			throw new CException(Yii::t(
				'zii',
				'"{operation}" is an unsupported class operation.',
				array('{operation}' => $config['class'])
			));
		}

		// name of the column should be unique
		if (!isset($this->extendedSummaryTypes[$name])) {
			$this->extendedSummaryTypes[$name] = Yii::createComponent($config);
			$this->extendedSummaryTypes[$name]->init();
		}
		return $this->extendedSummaryTypes[$name];
	}

	/**
	 *### .getColumnByName()
	 *
	 * Helper function to get a column by its name
	 *
	 * @param string $name
	 *
	 * @return TbDataColumn|null
	 */
	protected function getColumnByName($name)
	{
		foreach ($this->columns as $column) {
			if (strcmp($column->name, $name) === 0) {
				return $column;
			}
		}
		return null;
	}

	/**
	 * @return bool
	 */
	private function shouldEnableExtendedSummary()
	{
		return preg_match(
			'/extendedsummary/i',
			$this->template
		) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns']);
	}

	private function prepareDisplayingExtendedSummary()
	{
		$this->template .= "\n{extendedSummaryContent}";
		$this->displayExtendedSummary = true;
	}

	private function shouldEnableChart()
	{
		return !empty($this->chartOptions) && (bool)@$this->chartOptions['data'];
	}

	/**
	 * @return int
	 */
	private function hasData()
	{
		return $this->dataProvider->getItemCount();
	}

	private function enableChart()
	{
		$this->displayChart = true;
	}

	/**
	 * @return bool
	 */
	private function shouldEnableBulkActions()
	{
		return $this->bulkActions !== array() && isset($this->bulkActions['actionButtons']);
	}

	private function enableBulkActions()
	{
		if (!isset($this->bulkActions['class'])) {
			$this->bulkActions['class'] = 'booster.widgets.TbBulkActions';
		}

		$this->bulk = Yii::createComponent($this->bulkActions, $this);
		$this->bulk->init();
	}

	private function fillSelectionChangedProperty()
	{
		$this->selectionChanged = $this->makeDefaultSelectionChangedJavascript();
	}

	/**
	 * @return string
	 */
	private function makeDefaultSelectionChangedJavascript()
	{
		return 'js:function(id) {
			$("#"+id+" input[type=checkbox]").change();
		}';
	}

	/**
	 * This method will become `renderDataCell` after Yii 1.1.17 will be released
	 * @see https://github.com/yiisoft/yii/pull/3571
	 *
	 * @param CGridColumn $column
	 * @param integer $row
	 */
	private function renderDataCellProcessingSummariesIfNeeded($column, $row)
	{
		$value = $this->getRenderedDataCellValue($column, $row);

		if ($this->isExtendedSummaryEnabled())
			$this->processColumnValue($column, $value);

		echo $value;
	}

	/**
	 * @param CGridColumn $column
	 * @param integer $row
	 *
	 * @return string
	 */
	private function getRenderedDataCellValue($column, $row)
	{
		ob_start();
		$column->renderDataCell($row);
		return ob_get_clean();
	}

	/**
	 * @return bool
	 */
	private function isExtendedSummaryEnabled()
	{
		return $this->displayExtendedSummary && !empty($this->extendedSummary['columns']);
	}

}

/**
 *## TbOperation class
 *
 * Abstract class where all types of operations extend from
 *
 * @package booster.widgets.grids.operations
 */
abstract class TbOperation extends CWidget
{
	/**
	 * @var string $template the template to display label and value of the operation at the summary
	 */
	public $template = '{label}: {value}';

	/**
	 * @var int $value the resulted value of operation
	 */
	public $value = 0;

	/**
	 * @var string $label the label of the calculated value
	 */
	public $label;

	/**
	 * @var TbDataColumn $column
	 */
	public $column;

	/**
	 * Widget initialization
	 * @throws CException
	 */
	public function init()
	{
		if (null == $this->column) {
			throw new CException(Yii::t(
				'zii',
				'"{attribute}" attribute must be defined',
				array('{attribute}' => 'column')
			));
		}
	}

	/**
	 * Widget's run method
	 */
	public function run()
	{
		$this->displaySummary();
	}

	/**
	 * Process the row data value
	 *
	 * @param $value
	 *
	 * @return mixed
	 */
	abstract public function processValue($value);

	/**
	 * Displays the resulting summary
	 * @return mixed
	 */
	abstract public function displaySummary();

}

/**
 * TbSumOperation class
 *
 * Displays a total of specified column name.
 *
 * @package booster.widgets.grids.operations
 */
class TbSumOperation extends TbOperation
{
	/**
	 * @var float $total the total sum
	 */
	protected $total;

	/**
	 * @var array $supportedTypes the supported type of values
	 */
	protected $supportedTypes = array('raw', 'text', 'ntext', 'number');

	/**
	 * Widget's initialization method
	 * @throws CException
	 */
	public function init()
	{
		parent::init();

		if (!in_array($this->column->type, $this->supportedTypes)) {
			throw new CException(Yii::t(
				'zii',
				'Unsupported column type. Supported column types are: "{types}"',
				array(
					'{types}' => implode(', ', $this->supportedTypes)
				)
			));
		}
	}

	/**
	 * Extracts the digital part of the calculated value.
	 *
	 * @param int $value
	 *
	 * @return bool
	 */
	protected function extractNumber($value)
	{
		preg_match_all('/([+-]?[0-9]+[,\.]?)+/', $value, $matches);
		return !empty($matches[0]) && @$matches[0][0] ? $matches[0][0] : 0;
	}

	/**
	 * Process the value to calculate
	 *
	 * @param $value
	 *
	 * @return mixed|void
	 */
	public function processValue($value)
	{
		// remove html tags as we cannot access renderDataCellContent from the column
		$clean = strip_tags($value);
		$this->total += ((float)$this->extractNumber($clean));
	}

	/**
	 * Displays the summary
	 * @return mixed|void
	 */
	public function displaySummary()
	{
		echo strtr(
			$this->template,
			array(
				'{label}' => $this->label,
				'{value}' => $this->total === null ? '' : Yii::app()->format->format($this->total, $this->column->type)
			)
		);
	}
}

/**
 * TbCountOfTypeOperation class
 *
 * Renders a summary based on the count of specified types. For example, if a value has a type 'blue', this class will
 * count the number of times the value 'blue' has on that column.
 *
 * @package booster.widgets.grids.operations
 */
class TbCountOfTypeOperation extends TbOperation
{
	/**
	 * @var string $template
	 * @see parent class
	 */
	public $template = '{label}: {types}';

	/**
	 * @var string $typeTemplate holds the template of each calculated type
	 */
	public $typeTemplate = '{label}({value})';

	/**
	 * @var array $types hold the configuration of types to calculate. The configuration is set by an array which keys
	 * are the value types to count. You can set their 'label' independently.
	 *
	 * <pre>
	 *  'types' => array(
	 *      '0' => array('label' => 'zeros'),
	 *      '1' => array('label' => 'ones'),
	 *      '2' => array('label' => 'twos')
	 * </pre>
	 */
	public $types = array();

	/**
	 * Widget's initialization
	 * @throws CException
	 */
	public function init()
	{
		if (empty($this->types)) {
			throw new CException(Yii::t(
				'zii',
				'"{attribute}" attribute must be defined',
				array('{attribute}' => 'types')
			));
		}
		foreach ($this->types as $type) {
			if (!isset($type['label'])) {
				throw new CException(Yii::t('zii', 'The "label" of a type must be defined.'));
			}
		}
		parent::init();
	}

	/**
	 * (no phpDoc)
	 * @see TbOperation
	 *
	 * @param $value
	 *
	 * @return mixed|void
	 */
	public function processValue($value)
	{
		$clean = strip_tags($value);

		if (array_key_exists($clean, $this->types)) {
			if (!isset($this->types[$clean]['value'])) {
				$this->types[$clean]['value'] = 0;
			}
			$this->types[$clean]['value'] += 1;
		}
	}

	/**
	 * (no phpDoc)
	 * @see TbOperation
	 * @return mixed|void
	 */
	public function displaySummary()
	{
		$typesResults = array();
		foreach ($this->types as $type) {
			if (!isset($type['value'])) {
				$type['value'] = 0;
			}

			$typesResults[] = strtr(
				$this->typeTemplate,
				array('{label}' => $type['label'], '{value}' => $type['value'])
			);
		}
		echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
	}
}

/**
 *## TbPercentOfTypeOperation class
 *
 * Renders a summary based on the percent count of specified types. For example, if a value has a type 'blue', this class will
 * count the percentage number of times the value 'blue' has on that column.
 *
 * @package booster.widgets.grids.operations
 */
class TbPercentOfTypeOperation extends TbCountOfTypeOperation
{
	/**
	 * @var string $typeTemplate
	 * @see TbCountOfTypeOperation
	 */
	public $typeTemplate = '{label}({value}%)';

	/**
	 * @var integer $_total holds the total sum of the values. Required to get the percentage.
	 */
	protected $_total;

	/**
	 * @return mixed|void
	 * @see TbOperation
	 */
	public function displaySummary()
	{
		$typesResults = array();

		foreach ($this->types as $type) {
			if (!isset($type['value'])) {
				$type['value'] = 0;
			}

			$type['value'] = $this->getTotal() ? number_format((float)($type['value'] / $this->getTotal()) * 100, 1)
				: 0;
			$typesResults[] = strtr(
				$this->typeTemplate,
				array('{label}' => $type['label'], '{value}' => $type['value'])
			);
		}

		echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
	}

	/**
	 * Returns the total of types
	 * @return int holds
	 */
	protected function getTotal()
	{
		if (null == $this->_total) {
			$this->_total = 0;
			foreach ($this->types as $type) {
				if (isset($type['value'])) {
					$this->_total += $type['value'];
				}
			}
		}
		return $this->_total;
	}
}

/**
 *## TbPercentOfTypeGooglePieOperation class
 *
 * Displays a Google visualization  pie chart based on the percentage count of type.
 *
 * @package booster.widgets.grids.operations
 */
class TbPercentOfTypeGooglePieOperation extends TbPercentOfTypeOperation
{
	/**
	 * @var string $chartCssClass the class name of the layer holding the chart
	 */
	public $chartCssClass = 'bootstrap-operation-google-pie-chart';

	/**
	 * The options
	 * @var array $chartOptions
	 * @see https://google-developers.appspot.com/chart/interactive/docs/gallery/piechart
	 */
	public $chartOptions = array(
		'title' => 'Google Pie Chart'
	);

	/**
	 * @var array $data the configuration data of the chart
	 */
	protected $data = array();

	/**
	 * @see TbOperation
	 * @return mixed|void
	 */
	public function displaySummary() {
		
		$this->data[] = array('Label', 'Percent');

		foreach ($this->types as $type) {
			if (!isset($type['value'])) {
				$type['value'] = 0;
			}

			$this->data[] = $this->getTotal() ? array(
				$type['label'],
				(float)number_format(($type['value'] / $this->getTotal()) * 100, 1)
			) : 0;
		}
		$data = CJavaScript::jsonEncode($this->data);
		$options = CJavaScript::jsonEncode($this->chartOptions);
		echo "<div id='{$this->id}' class='{$this->chartCssClass}' data-data='{$data}' data-options='{$options}'></div>";

		$this->registerClientScript();
	}

	/**
	 * Registers required scripts
	 */
	public function registerClientScript()
	{
		$chart = Yii::createComponent(
			array(
				'class' => 'booster.widgets.TbGoogleVisualizationChart',
				'visualization' => 'PieChart',
				'containerId' => $this->getId(),
				'data' => $this->data,
				'options' => $this->chartOptions
			)
		);
		$chart->init();
		$chart->run();

		/**
		 * create custom chart update by using the global chart variable
		 * @see TbGoogleVisualizationChart
		 */
		$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
			'var $el = $("#' . $this->getId() . '");var data = $el.data("data");var opts = $el.data("options");
			data = google.visualization.arrayToDataTable(data);
			' . $chart->getId() . '=new google.visualization.PieChart(document.getElementById("' . $this->getId() . '"));
			' . $chart->getId() . '.draw(data,opts);';
	}

}

/**
 *## TbPercentOfTypeEasyPieOperation class
 *
 * Displays an chart based on jquery.easy.pie plugin
 *
 * @package booster.widgets.grids.operations
 */
class TbPercentOfTypeEasyPieOperation extends TbPercentOfTypeOperation
{
	/**
	 * @var string $chartCssClass the class of the layer containing the class
	 */
	public $chartCssClass = 'bootstrap-operation-easy-pie-chart';

	/**
	 * @var string $template
	 * @see TbOperation
	 */
	public $template = '<div style="clear:both">{label}: </div>{types}';

	/**
	 * @var string $typeTemplate
	 * @see parent class
	 */
	public $typeTemplate = '<div style="float:left;text-align:center;margin:2px"><div class="{class}" data-percent="{value}">{value}%</div><div>{label}</div></div>';

	// easy-pie-chart plugin options
	// @see https://github.com/rendro/easy-pie-chart#configuration-parameter
	public $chartOptions = array(
		'barColor' => '#ef1e25',
		// The color of the curcular bar. You can pass either a css valid color string like rgb,
		// rgba hex or string colors. But you can also pass a function that accepts the current
		// percentage as a value to return a dynamically generated color.
		'trackColor' => '#f2f2f2',
		// The color of the track for the bar, false to disable rendering.
		'scaleColor' => '#dfe0e0',
		// The color of the scale lines, false to disable rendering.
		'lineCap' => 'round',
		// Defines how the ending of the bar line looks like. Possible values are: butt, round and square.
		'lineWidth' => 5,
		// Width of the bar line in px.
		'size' => 80,
		// Size of the pie chart in px. It will always be a square.
		'animate' => false,
		// Time in milliseconds for a eased animation of the bar growing, or false to deactivate.
		'onStart' => 'js:$.noop',
		// Callback function that is called at the start of any animation (only if animate is not false).
		'onStop' => 'js:$.noop'
		// Callback function that is called at the end of any animation (only if animate is not false).
	);

	/**
	 * @see TbOperation
	 * @return mixed|void
	 */
	public function displaySummary()
	{
		$this->typeTemplate = strtr($this->typeTemplate, array('{class}' => $this->chartCssClass));

		parent::displaySummary();
		$this->registerClientScripts();
	}

	/**
	 * Register required scripts
	 */
	protected function registerClientScripts()
	{
        $booster = Booster::getBooster();
        $booster->registerAssetCss('easy-pie-chart.css');
        $booster->registerAssetJs('jquery.easy.pie.chart.js');

		$options = CJavaScript::encode($this->chartOptions);
		Yii::app()->getClientScript()->registerScript(
			__CLASS__ . '#percent-of-type-operation-simple-pie',
			'
					   $("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
				.easyPieChart(' . $options . ');
		'
		);
		$this->column->grid->componentsReadyScripts[__CLASS__] =
		$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
			'$("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
				.easyPieChart(' . $options . ');';
	}
}
