<?php



/**

 * toCSS Visitor

 *

 * @package Less

 * @subpackage visitor

 */

class Less_Visitor_toCSS extends Less_VisitorReplacing{



	private $charset;



	public function __construct(){

		parent::__construct();

	}



	/**

	 * @param Less_Tree_Ruleset $root

	 */

	public function run( $root ){

		return $this->visitObj($root);

	}



	public function visitRule( $ruleNode ){

		if( $ruleNode->variable ){

			return array();

		}

		return $ruleNode;

	}



	public function visitMixinDefinition($mixinNode){

		// mixin definitions do not get eval'd - this means they keep state

		// so we have to clear that state here so it isn't used if toCSS is called twice

		$mixinNode->frames = array();

		return array();

	}



	public function visitExtend(){

		return array();

	}



	public function visitComment( $commentNode ){

		if( $commentNode->isSilent() ){

			return array();

		}

		return $commentNode;

	}



	public function visitMedia( $mediaNode, &$visitDeeper ){

		$mediaNode->accept($this);

		$visitDeeper = false;



		if( !$mediaNode->rules ){

			return array();

		}

		return $mediaNode;

	}



	public function visitDirective( $directiveNode ){

		if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){

			return array();

		}

		if( $directiveNode->name === '@charset' ){

			// Only output the debug info together with subsequent @charset definitions

			// a comment (or @media statement) before the actual @charset directive would

			// be considered illegal css as it has to be on the first line

			if( isset($this->charset) && $this->charset ){



				//if( $directiveNode->debugInfo ){

				//	$comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");

				//	$comment->debugInfo = $directiveNode->debugInfo;

				//	return $this->visit($comment);

				//}





				return array();

			}

			$this->charset = true;

		}

		return $directiveNode;

	}



	public function checkPropertiesInRoot( $rulesetNode ){



		if( !$rulesetNode->firstRoot ){

			return;

		}



		foreach($rulesetNode->rules as $ruleNode){

			if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){

				$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);

				throw new Less_Exception_Compiler($msg);

			}

		}

	}





	public function visitRuleset( $rulesetNode, &$visitDeeper ){



		$visitDeeper = false;



		$this->checkPropertiesInRoot( $rulesetNode );



		if( $rulesetNode->root ){

			return $this->visitRulesetRoot( $rulesetNode );

		}



		$rulesets = array();

		$rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);





		// Compile rules and rulesets

		$nodeRuleCnt = count($rulesetNode->rules);

		for( $i = 0; $i < $nodeRuleCnt; ){

			$rule = $rulesetNode->rules[$i];



			if( property_exists($rule,'rules') ){

				// visit because we are moving them out from being a child

				$rulesets[] = $this->visitObj($rule);

				array_splice($rulesetNode->rules,$i,1);

				$nodeRuleCnt--;

				continue;

			}

			$i++;

		}





		// accept the visitor to remove rules and refactor itself

		// then we can decide now whether we want it or not

		if( $nodeRuleCnt > 0 ){

			$rulesetNode->accept($this);



			if( $rulesetNode->rules ){



				if( count($rulesetNode->rules) >  1 ){

					$this->_mergeRules( $rulesetNode->rules );

					$this->_removeDuplicateRules( $rulesetNode->rules );

				}



				// now decide whether we keep the ruleset

				if( $rulesetNode->paths ){

					//array_unshift($rulesets, $rulesetNode);

					array_splice($rulesets,0,0,array($rulesetNode));

				}

			}



		}





		if( count($rulesets) === 1 ){

			return $rulesets[0];

		}

		return $rulesets;

	}





	/**

	 * Helper function for visitiRuleset

	 *

	 * return array|Less_Tree_Ruleset

	 */

	private function visitRulesetRoot( $rulesetNode ){

		$rulesetNode->accept( $this );

		if( $rulesetNode->firstRoot || $rulesetNode->rules ){

			return $rulesetNode;

		}

		return array();

	}





	/**

	 * Helper function for visitRuleset()

	 *

	 * @return array

	 */

	private function visitRulesetPaths($rulesetNode){



		$paths = array();

		foreach($rulesetNode->paths as $p){

			if( $p[0]->elements[0]->combinator === ' ' ){

				$p[0]->elements[0]->combinator = '';

			}



			foreach($p as $pi){

				if( $pi->getIsReferenced() && $pi->getIsOutput() ){

					$paths[] = $p;

					break;

				}

			}

		}



		return $paths;

	}



	protected function _removeDuplicateRules( &$rules ){

		// remove duplicates

		$ruleCache = array();

		for( $i = count($rules)-1; $i >= 0 ; $i-- ){

			$rule = $rules[$i];

			if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){



				if( !isset($ruleCache[$rule->name]) ){

					$ruleCache[$rule->name] = $rule;

				}else{

					$ruleList =& $ruleCache[$rule->name];



					if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){

						$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );

					}



					$ruleCSS = $rule->toCSS();

					if( array_search($ruleCSS,$ruleList) !== false ){

						array_splice($rules,$i,1);

					}else{

						$ruleList[] = $ruleCSS;

					}

				}

			}

		}

	}



	protected function _mergeRules( &$rules ){

		$groups = array();



		//obj($rules);



		$rules_len = count($rules);

		for( $i = 0; $i < $rules_len; $i++ ){

			$rule = $rules[$i];



			if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){



				$key = $rule->name;

				if( $rule->important ){

					$key .= ',!';

				}



				if( !isset($groups[$key]) ){

					$groups[$key] = array();

				}else{

					array_splice($rules, $i--, 1);

					$rules_len--;

				}



				$groups[$key][] = $rule;

			}

		}





		foreach($groups as $parts){



			if( count($parts) > 1 ){

				$rule = $parts[0];

				$spacedGroups = array();

				$lastSpacedGroup = array();

				$parts_mapped = array();

				foreach($parts as $p){

					if( $p->merge === '+' ){

						if( $lastSpacedGroup ){

							$spacedGroups[] = self::toExpression($lastSpacedGroup);

						}

						$lastSpacedGroup = array();

					}

					$lastSpacedGroup[] = $p;

				}



				$spacedGroups[] = self::toExpression($lastSpacedGroup);

				$rule->value = self::toValue($spacedGroups);

			}

		}



	}



	public static function toExpression($values){

		$mapped = array();

		foreach($values as $p){

			$mapped[] = $p->value;

		}

		return new Less_Tree_Expression( $mapped );

	}



	public static function toValue($values){

		//return new Less_Tree_Value($values); ??



		$mapped = array();

		foreach($values as $p){

			$mapped[] = $p;

		}

		return new Less_Tree_Value($mapped);

	}

}



