<?php
/**
 *---------------------------------------------------------------------------------------
 * @package       VirtuePlanet Framework for Joomla!
 *---------------------------------------------------------------------------------------
 * @copyright     Copyright (C) 2012-2021 VirtuePlanet Services LLP. All rights reserved.
 * @license       GNU General Public License version 2 or later; see LICENSE.txt
 * @authors       Abhishek Das
 * @email         info@virtueplanet.com
 * @link          https://www.virtueplanet.com
 *---------------------------------------------------------------------------------------
 */
defined('_JEXEC') or die;

class VPFrameworkCompressorJs extends VPFrameworkCompressor
{
	protected $js             = '';
	protected $o_js           = '';
	protected $prepared_only  = false;
	protected $regexp_literal = null;
	
	public function proccess($js, $prepared_only = false)
	{
		$this->js   = $js;
		$this->o_js = $this->js;
		$this->prepared_only = $prepared_only;
		
		try
		{
			return $this->compress();
		}
		catch (Exception $e) 
		{
			return $this->o_js;
		}
	}
	
	public function getRegexLiteral()
	{
		return $this->regexp_literal;
	}
	
	protected function compress()
	{
		$double_quote  = $this->double_quote;
		$single_quote  = $this->single_quote;
		$comment_block = $this->comment_block;
		$comment_line  = $this->comment_line;
		
		// We have to do some manipulating with regexp literals; Their pattern is a little 'irregular' but 
		// they need to be escaped
		//
		// characters that can precede a regexp literal
		$expression1 = '[(.<>%,=:[!&|?+\-~*{;\r\n^]';
		// keywords that can precede a regex literal
		$expression2 = '\breturn|\bthrow|\btypeof|\bcase|\bdelete|\bdo|\belse|\bin|\binstanceof|\bnew|\bvoid';
		// actual regexp literal
		$expression3 = '/(?![/*])(?>(?(?=\\\\)\\\\.|\[(?>(?:\\\\.)?[^\]\r\n]*+)+?\])?[^\\\\/\r\n\[]*+)+?/';
		// ambiguous characters
		$expression4 = '[)}]';
		// methods and properties
		$expression5 = 'compile|exec|test|toString|constructor|global|ignoreCase|lastIndex|multiline|source';

		// regex for complete regexp literal
		$expression = "(?>(?=/)(?<={$expression1}|$expression2)(?<!\+\+|--){$expression3}" .
		              "|(?=/)(?<={$expression4}){$expression3}(?=\.(?>{$expression5})))";

		// control characters excluding \r, \
		$ws = '\x00-\x09\x0B\x0C\x0E-\x1F\x7F';

		// Remove spaces before regexp literals
		$regex = "#(?>[$ws ]*+(?(?=[^'\"/]*+(?<=[$ws ])/)[^'\"/$ws ]*+(?(?=['\"/])(?>$double_quote|$single_quote|$comment_block|$comment_line|$expression|/)?)"
		        . "|[^'\"/]*+(?>$double_quote|$single_quote|$comment_block|$comment_line|$expression|/)?))*?\K"
		        . "(?>(?=[$ws ]++/)(?:(?<=$expression1|$expression2)(?>[$ws ]++($expression3))|(?<=$expression4)(?>[$ws ]++($expression3))(?=\.(?>$expression5)))|$)#si";

		$this->js = $this->replace($regex, '$1$2', $this->js, '1');

		if ($this->prepared_only)
		{
			$this->regexp_literal = $expression;
			
			return $this->js;
		}

		// replace line comments with line feed
		$regex = "#(?>[^'\"/]*+(?>{$double_quote}|{$single_quote}|{$expression}|{$comment_block}|/(?![*/]))?)*?\K(?>{$comment_line}|$)#si";
		$this->js = $this->replace($regex, "\n", $this->js, '2');

		// replace block comments with single space
		$regex = "#(?>[^'\"/]*+(?>{$double_quote}|{$single_quote}|{$expression}|/(?![*/]))?)*?\K(?>{$comment_block}|$)#si";
		$this->js = $this->replace($regex, ' ', $this->js, '3');

		// convert carriage returns to line feeds
		$regex = "#(?>[^'\"/\\r]*+(?>$double_quote|$single_quote|$expression|/)?)*?\K(?>\\r\\n?|$)#si";
		$this->js = $this->replace($regex, "\n", $this->js, '4');

		// convert all other control characters to space
		$regex = "#(?>[^'\"/$ws]*+(?>$double_quote|$single_quote|$expression|/)?)*?\K(?>[$ws]++|$)#si";
		$this->js = $this->replace($regex, ' ', $this->js, '5');

		// replace runs of whitespace with single space or linefeed
		$regex = "#(?>[^'\"/\\n ]*+(?>{$double_quote}|{$single_quote}|{$expression}|[ \\n](?![ \\n])|/)?)*?\K(?:[ ]++(?=\\n)|\\n\K\s++|[ ]\K[ ]++|$)#si";
		$this->js = $this->replace($regex, '', $this->js, '6');

		// if regex literal ends line (without modifiers) insert semicolon
		$regex = "#(?>[/]?[^'\"/]*+(?>$double_quote|$single_quote|$expression(?!\\n))?)*?(?:$expression\K\\n(?![!\#%&`*./,:;<=>?@\^|~}\])\"'])|\K$)#si";
		$this->js = $this->replace($regex, ';', $this->js, '7');

		// clean up
		$this->js = substr($this->js, 0, -1);

		// regex for removing spaces
		// remove space except when a space is preceded and followed by a non-ASCII character or by an ASCII letter or digit, 
		// or by one of these characters \ $ _  ...ie., all ASCII characters except those listed.
		$comment_line  = '["\'!\#%&`()*./,:;<=>?@\[\]\^{}|~+\-]';
		$sp = "(?<=$comment_line) | (?=$comment_line)";

		// Non-ASCII characters
		$na = '[^\x00-\x7F]';

		// spaces to keep
		$keep1 = "(?<=[\$_a-z0-9\\\\]|$na) (?=[\$_a-z0-9\\\\]|$na)|(?<=\+) (?=\+)|(?<=-) (?=-)";

		// regex for removing linefeeds
		// remove linefeeds except if it precedes a non-ASCII character or an ASCII letter or digit or one of these 
		// characters: ! \ $ _ [ ( { + - and if it follows a non-ASCII character or an ASCII letter or digit or one of these 
		// characters: \ $ _ ] ) } + - " ' ...ie., all ASCII characters except those listed respectively
		$ln = '(?<=[!\#%&`*./,:;<=>?@\^|~{\[(])\n|\n(?=[\#%&`*./,:;<=>?@\^|~}\])"\'])';

		// line feeds to keep
		$keep2 = "(?<=[\$_a-z0-9\\\\\])}+\-\"']|$na)\n(?=[!\$_a-z0-9\\\\\[({+\-]|$na)";

		// remove unnecessary linefeeds and spaces
		$regex = "#(?>[^'\"/\\n ]*+(?>$double_quote|$single_quote|$expression|/|$keep1|$keep2)?)*?\K(?>$sp|$ln|$)#si";
		$this->js = $this->replace($regex, '', $this->js, '9');

		return trim($this->js);
	}
}