<?php
/**
 * @package    Joomla.Component.Builder
 *
 * @created    4th September, 2022
 * @author     Llewellyn van der Merwe <https://dev.vdm.io>
 * @git        Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
 * @copyright  Copyright (C) 2015 Vast Development Method. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;


use Joomla\CMS\Factory;
use Joomla\CMS\User\User;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Version;
use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Config;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Gui;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Extractor\Paths;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder\Reverse;
use VDM\Joomla\Componentbuilder\Compiler\Component\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Pathfix;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Placefix;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\ExtractorInterface;


/**
 * Compiler Custom Code Extractor
 * 
 * The custom script placeholders - we use the (xxx) to avoid detection it should be (***)
 * ##################################--->  PHP/JS  ---####################################
 * 
 * New Insert Code        = /xxx[INSERT>$$$$]xxx/                /xxx[/INSERT>$$$$]xxx/
 * New Replace Code    = /xxx[REPLACE>$$$$]xxx/               /xxx[/REPLACE>$$$$]xxx/
 * 
 * //////////////////////////////// when JCB adds it back //////////////////////////////////
 * JCB Add Inserted Code    = /xxx[INSERTED$$$$]xxx//xx23xx/          /xxx[/INSERTED$$$$]xxx/
 * JCB Add Replaced Code    = /xxx[REPLACED$$$$]xxx//xx25xx/          /xxx[/REPLACED$$$$]xxx/
 * 
 * /////////////////////////////// changeing existing custom code /////////////////////////
 * Update Inserted Code    = /xxx[INSERTED>$$$$]xxx//xx23xx/        /xxx[/INSERTED>$$$$]xxx/
 * Update Replaced Code    = /xxx[REPLACED>$$$$]xxx//xx25xx/        /xxx[/REPLACED>$$$$]xxx/
 * 
 * The custom script placeholders - we use the (==) to avoid detection it should be (--)
 * ###################################--->  HTML  ---#####################################
 * 
 * New Insert Code        = !==[INSERT>$$$$]==>                 !==[/INSERT>$$$$]==>
 * New Replace Code    = !==[REPLACE>$$$$]==>                !==[/REPLACE>$$$$]==>
 * 
 * ///////////////////////////////// when JCB adds it back ///////////////////////////////
 * JCB Add Inserted Code    =         
 * JCB Add Replaced Code    =         
 * 
 * //////////////////////////// changeing existing custom code ///////////////////////////
 * Update Inserted Code    = !==[INSERTED>$$$$]==>      !==[/INSERTED>$$$$]==>
 * Update Replaced Code    = !==[REPLACED>$$$$]==>      !==[/REPLACED>$$$$]==>
 * 
 * ////////23 is the ID of the code in the system don't change it!!!!!!!!!!!!!!!!!!!!!!!!!!
 * 
 * More info read: https://git.vdm.dev/joomla/Component-Builder/wiki/TIPS:-Custom-Code
 * 
 * @since 3.2.0
 */
class Extractor implements ExtractorInterface
{
	/**
	 * The placeholder keys
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $PKeys
		= [
			1 => 'REPLACE<>$$$$]',
			2 => 'INSERT<>$$$$]',
			3 => 'REPLACED<>$$$$]',
			4 => 'INSERTED<>$$$$]'
		];

	/**
	 * Current Joomla Version We are IN
	 *
	 * @var     int
	 * @since 3.2.0
	 **/
	protected int $currentVersion;

	/**
	 * The custom code in local files that already exist in system
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $existing = [];

	/**
	 * The custom code in local files that are new
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $new = [];

	/**
	 * The index of code already loaded
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $done = [];

	/**
	 * The search counter
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $counter = [1 => 0, 2 => 0];

	/**
	 * The file types to search
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $fileTypes = ['\.php', '\.js', '\.xml'];

	/**
	 * The local placeholders
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $placeholders;

	/**
	 * Today's date in SQL format
	 *
	 * @var      string
	 * @since 3.2.0
	 */
	protected string $today;

	/**
	 * Compiler Config
	 *
	 * @var    Config
	 * @since 3.2.0
	 **/
	protected Config $config;

	/**
	 * Compiler Customcode Gui
	 *
	 * @var    Gui
	 * @since 3.2.0
	 **/
	protected Gui $gui;

	/**
	 * Compiler Customcode Extractor Paths
	 *
	 * @var    Paths
	 * @since 3.2.0
	 **/
	protected Paths $paths;

	/**
	 * Compiler Placeholder Reverse
	 *
	 * @var    Reverse
	 * @since 3.2.0
	 **/
	protected Reverse $reverse;

	/**
	 * Compiler Component Placeholder
	 *
	 * @var    Placeholder
	 * @since 3.2.0
	 **/
	protected Placeholder $componentPlaceholder;

	/**
	 * Compiler Component Pathfix
	 *
	 * @var    Pathfix
	 * @since 3.2.0
	 **/
	protected Pathfix $pathfix;

	/**
	 * Current User Object
	 *
	 * @since 3.2.0
	 **/
	protected $user;

	/**
	 * Database object to query local DB
	 *
	 * @since 3.2.0
	 **/
	protected $db;

	/**
	 * Database object to query local DB
	 *
	 * @since 3.2.0
	 **/
	protected $app;

	/**
	 * Constructor.
	 *
	 * @param Config|null             $config      The compiler config object.
	 * @param Gui|null                $gui         The compiler customcode gui object.
	 * @param Paths|null              $paths       The compiler customcode extractor paths object.
	 * @param Reverse|null            $reverse     The compiler placeholder reverse object.
	 * @param Placeholder|null        $placeholder The compiler component placeholder object.
	 * @param Pathfix|null            $pathfix     The compiler path fixing object.
	 *
	 * @throws \Exception
	 * @since 3.2.0
	 */
	public function __construct(?Config $config = null, ?Gui $gui = null, ?Paths $paths = null,
		?Reverse $reverse = null, ?Placeholder $placeholder = null, ?Pathfix $pathfix = null)
	{
		$this->config = $config ?: Compiler::_('Config');
		$this->gui = $gui ?: Compiler::_('Customcode.Gui');
		$this->paths = $paths ?: Compiler::_('Customcode.Extractor.Paths');
		$this->reverse = $reverse ?: Compiler::_('Placeholder.Reverse');
		$this->componentPlaceholder = $placeholder ?: Compiler::_('Component.Placeholder');
		$this->pathfix = $pathfix ?: Compiler::_('Utilities.Pathfix');
		$this->user = Factory::getUser();
		$this->db = Factory::getDbo();
		$this->app = Factory::getApplication();

		// set today's date
		$this->today = Factory::getDate()->toSql();

		// set some local placeholders
		$placeholders = array_flip(
			$this->componentPlaceholder->get()
		);

		$placeholders[StringHelper::safe(
			$this->config->component_code_name, 'F'
		) . 'Helper::'] = Placefix::_('Component') . 'Helper::';

		$placeholders['COM_' . StringHelper::safe(
			$this->config->component_code_name, 'U'
		)] = 'COM_' . Placefix::_('COMPONENT');

		$placeholders['com_' . $this->config->component_code_name] = 'com_' . Placefix::_('component');

		// set the local placeholders
		$this->placeholders = array_reverse($placeholders, true);

		// set the current version
		$this->currentVersion = (int) Version::MAJOR_VERSION;
	}

	/**
	 * get the custom code from the local files
	 *
	 * @return  void
	 * @since 3.2.0
	 */
	public function run()
	{
		// we must first store the current working directory
		$joomla  = getcwd();

		foreach ($this->paths->active as $target => $path)
		{
			// we are changing the working directory to the component path
			chdir($path);
			foreach ($this->fileTypes as $type)
			{
				// get a list of files in the current directory tree (only PHP, JS and XML for now)
				$files = Folder::files('.', $type, true, true);

				// check if files found
				if (ArrayHelper::check($files))
				{
					foreach ($files as $file)
					{
						// search the file
						$this->searchFileContent($file, $target);

						// insert new code
						$this->insert(100);

						// update existing custom code
						$this->update(30);
					}
				}
			}
		}

		// change back to Joomla working directory
		chdir($joomla);

		// make sure all code is stored
		$this->insert();
		// update existing custom code
		$this->update();
	}

	/**
	 * search a file for placeholders and store result
	 *
	 * @param   string  $file          The file path to search
	 *
	 * @return  array    on success
	 * @since 3.2.0
	 */
	protected function searchFileContent(&$file, &$target)
	{
		// we add a new search for the GUI CODE Blocks
		$this->gui->search($file, $this->placeholders, $this->today, $target);

		// reset each time per file
		$loadEndFingerPrint = false;
		$endFingerPrint = [];
		$fingerPrint = [];
		$codeBucket = [];
		$pointer = [];
		$reading = [];
		$reader = 0;

		// reset found Start type
		$commentType = 0;

		// make sure we have the path correct (the script file is not in admin path for example)
		// there may be more... will nead to keep our eye on this... since files could be moved during install
		$file = str_replace('./', '', (string) $file); # TODO (windows path issues)
		$path = $file !== 'script.php' ? $target . '/' . $file : $file;

		// now we go line by line
		foreach (new \SplFileObject($file) as $lineNumber => $lineContent)
		{
			// we must keep last few lines to dynamic find target entry later
			$fingerPrint[$lineNumber] = trim($lineContent);

			// load the end fingerprint
			if ($loadEndFingerPrint)
			{
				$endFingerPrint[$lineNumber] = trim($lineContent);
			}

			foreach ($this->PKeys as $type => $search)
			{
				$i     = (int) ($type == 3 || $type == 4) ? 2 : 1;
				$_type = (int) ($type == 1 || $type == 3) ? 1 : 2;

				if ($reader === 0 || $reader === $i)
				{
					$targetKey = $type;

					$start     = '/***[' . $search . '***/';
					$end       = '/***[/' . $search . '***/';
					$startHTML = '<!--[' . $search . '-->';
					$endHTML   = '<!--[/' . $search . '-->';

					// check if the ending placeholder was found
					if (isset($reading[$targetKey]) && $reading[$targetKey]
						&& ((trim((string) $lineContent) === $end
							|| strpos((string) $lineContent, $end) !== false)
							|| (trim((string) $lineContent) === $endHTML
							|| strpos((string) $lineContent, $endHTML) !== false)))
					{
						// trim the placeholder and if there is still data then load it
						if (isset($endReplace)
							&& ($_line = $this->addLineChecker($endReplace, 2, $lineContent)) !== false)
						{
							$codeBucket[$pointer[$targetKey]][] = $_line;
						}

						// deactivate the reader
						$reading[$targetKey] = false;

						if ($_type == 2)
						{
							// deactivate search
							$reader = 0;
						}
						else
						{
							// activate fingerPrint for replacement end target
							$loadEndFingerPrint = true;
							$backupTargetKey    = $targetKey;
							$backupI            = $i;
						}

						// all new records we can do a bulk insert
						if ($i === 1)
						{
							// end the bucket info for this code block
							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								(int) $lineNumber
							);   // 'toline'

							// first reverse engineer this code block
							$c0de = $this->reverse->engine(
								implode('', $codeBucket[$pointer[$targetKey]]),
								$this->placeholders, $target
							);

							$this->new[$pointer[$targetKey]][]
							      = $this->db->quote(
								base64_encode((string) $c0de)
							);  // 'code'

							if ($_type == 2)
							{
								// load the last value
								$this->new[$pointer[$targetKey]][]
									= $this->db->quote(0); // 'hashendtarget'
							}
						}
						// the record already exist so we must update instead
						elseif ($i === 2)
						{
							// end the bucket info for this code block
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('to_line') . ' = '
								. $this->db->quote($lineNumber);

							// first reverse engineer this code block
							$c0de = $this->reverse->engine(
								implode('', $codeBucket[$pointer[$targetKey]]),
								$this->placeholders, $target,
								$this->existing[$pointer[$targetKey]]['id']
							);

							$this->existing[$pointer[$targetKey]]['fields'][]
							      = $this->db->quoteName('code') . ' = '
								. $this->db->quote(base64_encode((string) $c0de));

							if ($_type == 2)
							{
								// load the last value
								$this->existing[$pointer[$targetKey]]['fields'][]
									= $this->db->quoteName('hashendtarget')
									. ' = ' . $this->db->quote(0);
							}
						}
					}

					// check if the endfingerprint is ready to save
					if (count((array) $endFingerPrint) === 3)
					{
						$hashendtarget = '3__' . md5(
								implode('', $endFingerPrint)
							);

						// all new records we can do a bulk insert
						if ($i === 1)
						{
							// load the last value
							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$hashendtarget
							); // 'hashendtarget'
						}
						// the record already exist so we must update
						elseif ($i === 2)
						{
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('hashendtarget') . ' = '
								. $this->db->quote($hashendtarget);
						}

						// reset the needed values
						$endFingerPrint     = [];
						$loadEndFingerPrint = false;

						// deactivate reader (to allow other search)
						$reader = 0;
					}

					// then read in the code
					if (isset($reading[$targetKey]) && $reading[$targetKey])
					{
						$codeBucket[$pointer[$targetKey]][] = $lineContent;
					}

					// see if the custom code line starts now with PHP/JS comment type
					if ((!isset($reading[$targetKey]) || !$reading[$targetKey])
						&& (($i === 1 && trim((string) $lineContent) === $start)
							|| strpos((string) $lineContent, $start) !== false))
					{
						$commentType  = 1; // PHP/JS type
						$startReplace = $start;
						$endReplace   = $end;
					}
					// see if the custom code line starts now with HTML comment type
					elseif ((!isset($reading[$targetKey])
							|| !$reading[$targetKey])
						&& (($i === 1 && trim((string) $lineContent) === $startHTML)
							|| strpos((string) $lineContent, $startHTML) !== false))
					{
						$commentType  = 2; // HTML type
						$startReplace = $startHTML;
						$endReplace   = $endHTML;
					}

					// check if the starting place holder was found
					if ($commentType > 0)
					{
						// if we have all on one line we have a problem (don't load it TODO)
						if (strpos((string) $lineContent, (string) $endReplace) !== false)
						{
							// reset found comment type
							$commentType = 0;
							$this->app->enqueueMessage(
								Text::_('COM_COMPONENTBUILDER_HR_HTHREECUSTOM_CODES_WARNINGHTHREE'),
								'Warning'
							);
							$this->app->enqueueMessage(
								Text::sprintf('COM_COMPONENTBUILDER_WE_FOUND_DYNAMIC_CODE_BALL_IN_ONE_LINEB_AND_IGNORED_IT_PLEASE_REVIEW_S_FOR_MORE_DETAILS',
									$path
								), 'Warning'
							);
							continue;
						}

						// do a quick check to insure we have an id
						$id = false;
						if ($i === 2)
						{
							$id = $this->getSystemID(
								$lineContent,
								array(1 => $start, 2 => $startHTML),
								$commentType
							);
						}

						if ($i === 2 && $id > 0)
						{
							// make sure we update it only once even if found again.
							if (isset($this->done[$id]))
							{
								// reset found comment type
								$commentType = 0;
								continue;
							}
							// store the id to avoid duplication
							$this->done[$id] = (int) $id;
						}

						// start replace
						$startReplace = $this->setStartReplace(
							$id, $commentType, $startReplace
						);

						// set active reader (to lock out other search)
						$reader = $i;

						// set pointer
						$pointer[$targetKey] = $this->counter[$i];

						// activate the reader
						$reading[$targetKey] = true;

						// start code bucket
						$codeBucket[$pointer[$targetKey]] = [];

						// trim the placeholder and if there is still data then load it
						if ($_line = $this->addLineChecker(
							$startReplace, 1, $lineContent
						))
						{
							$codeBucket[$pointer[$targetKey]][] = $_line;
						}

						// get the finger print around the custom code
						$inFinger   = count($fingerPrint);
						$getFinger  = $inFinger - 1;
						$hasharray  = array_slice(
							$fingerPrint, -$inFinger, $getFinger, true
						);
						$hasleng    = count($hasharray);
						$hashtarget = $hasleng . '__' . md5(
								implode('', $hasharray)
							);

						// for good practice
						$this->pathfix->set($path);

						// all new records we can do a bulk insert
						if ($i === 1 || !$id)
						{
							// start the bucket for this code
							$this->new[$pointer[$targetKey]] = [];
							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$path
							);   // 'path'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								(int) $_type
							);  // 'type'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								1
							); // 'target'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$this->currentVersion
							); // 'joomla_version'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$commentType
							);  // 'comment_type'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								(int) $this->config->component_id
							); // 'component'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								1
							); // 'published'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$this->today
							);   // 'created'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								(int) $this->user->id
							); // 'created_by'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								1
							); // 'version'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								1
							); // 'access'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								$hashtarget
							);  // 'hashtarget'

							$this->new[$pointer[$targetKey]][]
								= $this->db->quote(
								(int) $lineNumber
							);  // 'fromline'

						}
						// the record already exist so we must update instead
						elseif ($i === 2 && $id > 0)
						{
							// start the bucket for this code
							$this->existing[$pointer[$targetKey]] = [];
							$this->existing[$pointer[$targetKey]]['id']
								= (int) $id;
							$this->existing[$pointer[$targetKey]]['conditions'] = [];
							$this->existing[$pointer[$targetKey]]['conditions'][]
								= $this->db->quoteName('id') . ' = '
								. $this->db->quote($id);
							$this->existing[$pointer[$targetKey]]['fields'] = [];
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('path') . ' = '
								. $this->db->quote($path);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('type') . ' = '
								. $this->db->quote($_type);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('comment_type') . ' = '
								. $this->db->quote($commentType);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('component') . ' = '
								. $this->db->quote($this->config->component_id);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('from_line') . ' = '
								. $this->db->quote($lineNumber);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('modified') . ' = '
								. $this->db->quote($this->today);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('modified_by') . ' = '
								. $this->db->quote($this->user->id);
							$this->existing[$pointer[$targetKey]]['fields'][]
								= $this->db->quoteName('hashtarget') . ' = '
								. $this->db->quote($hashtarget);
						}
						else // this should actualy never happen
						{
							// de activate the reader
							$reading[$targetKey] = false;
							$reader              = 0;
						}

						// reset found comment type
						$commentType = 0;
						// update the counter
						$this->counter[$i]++;
					}
				}
			}

			// make sure only a few lines is kept at a time
			if (count((array) $fingerPrint) > 10)
			{
				$fingerPrint = array_slice($fingerPrint, -6, 6, true);
			}
		}

		// if the code is at the end of the page and there were not three more lines
		if (count((array) $endFingerPrint) > 0 || $loadEndFingerPrint)
		{
			if (count((array) $endFingerPrint) > 0)
			{
				$leng          = count($endFingerPrint);
				$hashendtarget = $leng . '__' . md5(
						implode('', $endFingerPrint)
					);
			}
			else
			{
				$hashendtarget = 0;
			}

			// all new records we can do a buldk insert
			if ($backupI === 1)
			{
				// load the last value
				$this->new[$pointer[$backupTargetKey]][]
					= $this->db->quote($hashendtarget); // 'hashendtarget'
			}
			// the record already exist so we must use module to update
			elseif ($backupI === 2)
			{
				$this->existing[$pointer[$backupTargetKey]]['fields'][]
					= $this->db->quoteName('hashendtarget') . ' = '
					. $this->db->quote($hashendtarget);
			}
		}
	}

	/**
	 * Insert the code
	 *
	 * @param   int  $when  To set when to update
	 *
	 * @return  void
	 * @since 3.2.0
	 */
	protected function insert(int $when = 1)
	{
		if (ArrayHelper::check($this->new) >= $when)
		{
			// Create a new query object.
			$query    = $this->db->getQuery(true);
			$continue = false;
			// Insert columns.
			$columns = array('path', 'type', 'target', 'joomla_version', 'comment_type',
				'component', 'published', 'created', 'created_by',
				'version', 'access', 'hashtarget', 'from_line',
				'to_line', 'code', 'hashendtarget');
			// Prepare the insert query.
			$query->insert(
				$this->db->quoteName('#__componentbuilder_custom_code')
			);
			$query->columns($this->db->quoteName($columns));
			foreach ($this->new as $values)
			{
				if (count((array) $values) == 15)
				{
					$query->values(implode(',', $values));
					$continue = true;
				}
				else
				{
					// TODO line mismatch... should not happen
				}
			}
			// clear the values array
			$this->new = [];
			if (!$continue)
			{
				return; // insure we don't continue if no values were loaded
			}
			// Set the query using our newly populated query object and execute it.
			$this->db->setQuery($query);
			$this->db->execute();
		}
	}

	/**
	 * Update the code
	 *
	 * @param   int  $when  To set when to update
	 *
	 * @return  void
	 * @since 3.2.0
	 */
	protected function update(int $when = 1)
	{
		if (ArrayHelper::check($this->existing) >= $when)
		{
			foreach ($this->existing as $code)
			{
				// Create a new query object.
				$query = $this->db->getQuery(true);
				// Prepare the update query.
				$query->update(
					$this->db->quoteName('#__componentbuilder_custom_code')
				)->set($code['fields'])->where($code['conditions']);
				// Set the query using our newly populated query object and execute it.
				$this->db->setQuery($query);
				$this->db->execute();
			}
			// clear the values array
			$this->existing = [];
		}
	}

	/**
	 * set the start replace placeholder
	 *
	 * @param   int     $id            The comment id
	 * @param   int     $commentType   The comment type
	 * @param   string  $startReplace  The main replace string
	 *
	 * @return  string    on success
	 * @since 3.2.0
	 */
	protected function setStartReplace(int $id, int $commentType, string $startReplace): string
	{
		if ($id > 0)
		{
			switch ($commentType)
			{
				case 1: // the PHP & JS type
					$startReplace .= '/*' . $id . '*/';
					break;
				case 2: // the HTML type
					$startReplace .= '<!--' . $id . '-->';
					break;
			}
		}

		return $startReplace;
	}

	/**
	 * Check if this line should be added
	 *
	 * @param   string  $replaceKey   The key to remove from line
	 * @param   int     $type         The line type
	 * @param   string  $lineContent  The line to check
	 *
	 * @return  bool|int true    on success
	 * @since 3.2.0
	 */
	protected function addLineChecker(string $replaceKey, int $type, string $lineContent)
	{
		$check = explode($replaceKey, $lineContent);
		switch ($type)
		{
			case 1:
				// beginning of code
				if (isset($check[1]) && StringHelper::check($check[1]))
				{
					return trim($check[1]);
				}
				break;
			case 2:
				// end of code
				if (isset($check[0]) && StringHelper::check($check[0]))
				{
					return trim($check[0]);
				}
				break;
		}

		return false;
	}

	/**
	 * search for the system id in the line given
	 *
	 * @param   string  $lineContent   The file path to search
	 * @param   array   $placeholders  The values to search for
	 * @param   int     $commentType   The comment type
	 *
	 * @return  mixed    on success
	 * @since 3.2.0
	 */
	protected function getSystemID(string &$lineContent, array $placeholders, int $commentType)
	{
		$trim = '/';
		if ($commentType == 2)
		{
			$trim = '<!--';
		}
		// remove place holder from content
		$string = trim(
			str_replace($placeholders[$commentType] . $trim, '', $lineContent)
		);
		// now get all numbers
		$numbers = [];
		preg_match_all('!\d+!', $string, $numbers);
		// return the first number
		if (isset($numbers[0])
			&& ArrayHelper::check(
				$numbers[0]
			))
		{
			return reset($numbers[0]);
		}

		return false;
	}

}

