<?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 = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0;
		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 );
	}
}
