<?php declare(strict_types = 1);

namespace SlevomatCodingStandard\Helpers;

use PHP_CodeSniffer\Files\File;
use function array_key_exists;
use function array_merge;
use function in_array;
use const T_CLOSE_CURLY_BRACKET;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSE_SQUARE_BRACKET;
use const T_DOLLAR;
use const T_DOUBLE_COLON;
use const T_NULLSAFE_OBJECT_OPERATOR;
use const T_OBJECT_OPERATOR;
use const T_OPEN_CURLY_BRACKET;
use const T_OPEN_SQUARE_BRACKET;
use const T_PARENT;
use const T_SELF;
use const T_STATIC;
use const T_VARIABLE;

/**
 * @internal
 */
class IdentificatorHelper
{

	public static function getContent(File $phpcsFile, int $startPointer, int $endPointer): string
	{
		$tokens = $phpcsFile->getTokens();

		$variableContent = '';
		for ($i = $startPointer; $i <= $endPointer; $i++) {
			if (in_array($tokens[$i]['code'], TokenHelper::$ineffectiveTokenCodes, true)) {
				continue;
			}

			$variableContent .= $tokens[$i]['content'];
		}

		return $variableContent;
	}

	public static function findStartPointer(File $phpcsFile, int $endPointer): ?int
	{
		$tokens = $phpcsFile->getTokens();

		if (in_array($tokens[$endPointer]['code'], TokenHelper::getNameTokenCodes(), true)) {
			/** @var int $previousPointer */
			$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $endPointer - 1);
			if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
				$pointerBeforeOperator = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
				if ($tokens[$pointerBeforeOperator]['code'] !== T_CLOSE_PARENTHESIS) {
					return self::getStartPointerBeforeOperator($phpcsFile, $previousPointer);
				}
			}

			return $endPointer;
		}

		if (in_array($tokens[$endPointer]['code'], [T_CLOSE_CURLY_BRACKET, T_CLOSE_SQUARE_BRACKET], true)) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $tokens[$endPointer]['bracket_opener']);
		}

		if ($tokens[$endPointer]['code'] === T_VARIABLE) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $endPointer);
		}

		return null;
	}

	public static function findEndPointer(File $phpcsFile, int $startPointer): ?int
	{
		$tokens = $phpcsFile->getTokens();

		$nameTokenCodes = TokenHelper::getNameTokenCodes();

		if (in_array($tokens[$startPointer]['code'], $nameTokenCodes, true)) {
			$startPointer = TokenHelper::findNextExcluding($phpcsFile, $nameTokenCodes, $startPointer + 1) - 1;
		} elseif ($tokens[$startPointer]['code'] === T_DOLLAR) {
			$startPointer = TokenHelper::findNextEffective($phpcsFile, $startPointer + 1);
		}

		/** @var int $nextPointer */
		$nextPointer = TokenHelper::findNextEffective($phpcsFile, $startPointer + 1);

		if (
			in_array($tokens[$startPointer]['code'], array_merge([T_SELF, T_STATIC, T_PARENT], $nameTokenCodes), true)
			&& $tokens[$nextPointer]['code'] === T_DOUBLE_COLON
		) {
			return self::getEndPointerAfterOperator($phpcsFile, $nextPointer);
		}

		if ($tokens[$startPointer]['code'] === T_VARIABLE) {
			if (in_array($tokens[$nextPointer]['code'], [T_DOUBLE_COLON, T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR], true)) {
				return self::getEndPointerAfterOperator($phpcsFile, $nextPointer);
			}

			if ($tokens[$nextPointer]['code'] === T_OPEN_SQUARE_BRACKET) {
				return self::getEndPointerAfterVariablePart($phpcsFile, $startPointer);
			}

			return $startPointer;
		}

		return null;
	}

	private static function getStartPointerBeforeOperator(File $phpcsFile, int $operatorPointer): int
	{
		$tokens = $phpcsFile->getTokens();

		$nameTokenCodes = TokenHelper::getNameTokenCodes();

		/** @var int $previousPointer */
		$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $operatorPointer - 1);

		if (in_array($tokens[$previousPointer]['code'], $nameTokenCodes, true)) {
			$previousPointer = TokenHelper::findPreviousExcluding($phpcsFile, $nameTokenCodes, $previousPointer - 1) + 1;
		}

		if (
			$tokens[$operatorPointer]['code'] === T_DOUBLE_COLON
			&& in_array($tokens[$previousPointer]['code'], array_merge([T_SELF, T_STATIC, T_PARENT], $nameTokenCodes), true)
		) {
			return $previousPointer;
		}

		if (in_array($tokens[$previousPointer]['code'], $nameTokenCodes, true)) {
			/** @var int $possibleOperatorPointer */
			$possibleOperatorPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
			if (in_array($tokens[$possibleOperatorPointer]['code'], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR], true)) {
				return self::getStartPointerBeforeOperator($phpcsFile, $possibleOperatorPointer);
			}
		}

		if (in_array($tokens[$previousPointer]['code'], [T_CLOSE_CURLY_BRACKET, T_CLOSE_SQUARE_BRACKET], true)) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $tokens[$previousPointer]['bracket_opener']);
		}

		return self::getStartPointerBeforeVariablePart($phpcsFile, $previousPointer);
	}

	private static function getStartPointerBeforeVariablePart(File $phpcsFile, int $variablePartPointer): int
	{
		$tokens = $phpcsFile->getTokens();

		/** @var int $previousPointer */
		$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $variablePartPointer - 1);

		if ($tokens[$previousPointer]['code'] === T_DOLLAR) {
			/** @var int $previousPointer */
			$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
		}

		if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
			return self::getStartPointerBeforeOperator($phpcsFile, $previousPointer);
		}

		if ($tokens[$previousPointer]['code'] === T_CLOSE_SQUARE_BRACKET) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $tokens[$previousPointer]['bracket_opener']);
		}

		if (
			$tokens[$previousPointer]['code'] === T_CLOSE_CURLY_BRACKET
			&& !array_key_exists('scope_condition', $tokens[$previousPointer])
		) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $tokens[$previousPointer]['bracket_opener']);
		}

		if (in_array($tokens[$previousPointer]['code'], array_merge([T_VARIABLE], TokenHelper::getNameTokenCodes()), true)) {
			return self::getStartPointerBeforeVariablePart($phpcsFile, $previousPointer);
		}

		return $variablePartPointer;
	}

	private static function getEndPointerAfterOperator(File $phpcsFile, int $operatorPointer): int
	{
		$tokens = $phpcsFile->getTokens();

		/** @var int $nextPointer */
		$nextPointer = TokenHelper::findNextEffective($phpcsFile, $operatorPointer + 1);

		if ($tokens[$nextPointer]['code'] === T_DOLLAR) {
			/** @var int $nextPointer */
			$nextPointer = TokenHelper::findNextEffective($phpcsFile, $nextPointer + 1);
		}

		if ($tokens[$nextPointer]['code'] === T_OPEN_CURLY_BRACKET) {
			return self::getEndPointerAfterVariablePart($phpcsFile, $tokens[$nextPointer]['bracket_closer']);
		}

		return self::getEndPointerAfterVariablePart($phpcsFile, $nextPointer);
	}

	private static function getEndPointerAfterVariablePart(File $phpcsFile, int $variablePartPointer): int
	{
		$tokens = $phpcsFile->getTokens();

		/** @var int $nextPointer */
		$nextPointer = TokenHelper::findNextEffective($phpcsFile, $variablePartPointer + 1);

		if (in_array($tokens[$nextPointer]['code'], [T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
			return self::getEndPointerAfterOperator($phpcsFile, $nextPointer);
		}

		if ($tokens[$nextPointer]['code'] === T_OPEN_SQUARE_BRACKET) {
			return self::getEndPointerAfterVariablePart($phpcsFile, $tokens[$nextPointer]['bracket_closer']);
		}

		return $variablePartPointer;
	}

}
