<?php

declare(strict_types=1);

namespace Oktagon\WooCommerce\XConnect;

class Lexer
{
    const EOF_IDENTIFIER = 'eof-id';
    const E_IDENTIFIER = 'e-id';

    const T_AND = 't_and';
    const T_BOOLEAN = 't_boolean';
    const T_CLOSING_PARENTHESIS = 't_closing_parenthesis';
    const T_COMMA = 't_comma';
    const T_DIFFERS = 't_differs';
    const T_EQUALS = 't_equals';
    const T_FLOAT = 't_float';
    const T_FUNCTION_NAME = 't_function_name';
    const T_GREATER = 't_greater';
    const T_GREATER_OR_EQUALS = 't_greater_or_equals';
    const T_INTEGER = 't_integer';
    const T_LESSER = 't_lesser';
    const T_LESSER_OR_EQUALS = 't_lesser_or_equals';
    const T_NEGATION = 't_negation';
    const T_NEWLINE = 't_newline';
    const T_NOT_EQUALS = 't_not_equals';
    const T_OPENING_PARENTHESIS = 't_opening_parenthesis';
    const T_OR = 't_OR';
    const T_STRING = 't_string';
    const T_VARIABLE = 't_variable';

    const N_EXPRESSION = 'n_expression';
    const N_FUNCTION_CALL = 'n_function_call';
    const N_LOGICAL_EXPRESSION = 'n_logical_expression';
    const N_LOGICAL_EXPRESSIONS = 'n_logical_expressions';
    const N_OPERATOR = 'n_operator';
    const N_OPTIONAL_LOGICAL_EXPRESSIONS = 'n_optional_logical_expressions';
    const N_OPTIONAL_PARAMETERS = 'n_optional_parameters';
    const N_PARAMETERS = 'n_parameters';
    const N_PARAMETER = 'n_parameter';
    const N_SCALAR = 'n_scalar';
    const N_START = 'n_start';
    const N_VARIABLE = 'n_variable';

    private string $input;
    private int $maxIndex;

    public function setInput(string $input): void
    {
        $this->input = $input;
        $this->maxIndex = strlen($this->input);
    }

    public function getToken(array $token)
    {
        $type = $token[0];
        $start = $token[1];
        $end = $token[2];
        if (
            $start <= $this->maxIndex
            && $end <= $this->maxIndex
        ) {
            switch ($type) {
                case Lexer::T_FLOAT:
                    return floatval(
                        str_replace(
                            ',',
                            '.',
                            substr(
                                $this->input,
                                $start,
                                $end - $start
                            )
                        )
                    );
                case Lexer::T_INTEGER:
                    return intval(
                        substr(
                            $this->input,
                            $start,
                            $end - $start
                        )
                    );
                case Lexer::T_BOOLEAN:
                    return strtolower(
                        substr(
                            $this->input,
                            $start,
                            $end - $start
                        )
                    ) === 'true';
                case Lexer::T_VARIABLE:
                    return substr(
                        $this->input,
                        $start + 1,
                        $end - ($start + 1)
                    );
                case Lexer::T_STRING:
                    return substr(
                        $this->input,
                        $start + 1,
                        ($end - 1) - ($start + 1)
                    );
                default:
                    return substr(
                        $this->input,
                        $start,
                        $end - $start
                    );
            }
        }
        return null;
    }

    /**
     * @throws \Exception
     */
    public function fetchTokenWithForward(int $index): ?array
    {
        while (true) {
            $result = $this->fetchToken($index);
            // Move?
            if ($result[0] !== null) {
                $index = $result[0];
            } elseif ($result[1]) {
                return $result[1];
            } else {
                return null;
            }
        }
    }

    private function fetchToken(int $index): array
    {
        if ($index >= $this->maxIndex) {
            return [null, null];
        }

        $input = substr(
            $this->input,
            $index
        );

        $token = null;
        $move = null;

        $matches = [];
        if (preg_match('/^[0-9]+(\.|,)[0-9]+/', $input, $matches) === 1) {
            $token = [
                Lexer::T_FLOAT,
                $index,
                $index + strlen($matches[0])
            ];
        } elseif (preg_match('/^[0-9]+/', $input, $matches) === 1) {
            $token = [
                Lexer::T_INTEGER,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\'[^\']+\'/', $input, $matches) === 1) {
            $token = [
                Lexer::T_STRING,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^(false|true)/i', $input, $matches) === 1) {
            $token = [
                Lexer::T_BOOLEAN,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^<=/', $input, $matches) === 1) {
            $token = [
                Lexer::T_LESSER_OR_EQUALS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^<>/', $input, $matches) === 1) {
            $token = [
                Lexer::T_DIFFERS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^</', $input, $matches) === 1) {
            $token = [
                Lexer::T_LESSER,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^>=/', $input, $matches) === 1) {
            $token = [
                Lexer::T_GREATER_OR_EQUALS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^>/', $input, $matches) === 1) {
            $token = [
                Lexer::T_GREATER,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^&/', $input, $matches) === 1) {
            $token = [
                Lexer::T_AND,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\|/', $input, $matches) === 1) {
            $token = [
                Lexer::T_OR,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^=/', $input, $matches) === 1) {
            $token = [
                Lexer::T_EQUALS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^!=/', $input, $matches) === 1) {
            $token = [
                Lexer::T_NOT_EQUALS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^!/', $input, $matches) === 1) {
            $token = [
                Lexer::T_NEGATION,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\$[a-z][a-zA-Z]*/', $input, $matches) === 1) {
            $token = [
                Lexer::T_VARIABLE,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^[a-zA-Z]+/', $input, $matches) === 1) {
            $token = [
                Lexer::T_FUNCTION_NAME,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\(/', $input, $matches) === 1) {
            $token = [
                Lexer::T_OPENING_PARENTHESIS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\)/', $input, $matches) === 1) {
            $token = [
                Lexer::T_CLOSING_PARENTHESIS,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^,/', $input, $matches) === 1) {
            $token = [
                Lexer::T_COMMA,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^\n+/', $input, $matches) === 1) {
            $token = [
                Lexer::T_NEWLINE,
                $index,
                $index + strlen($matches[0]),
            ];
        } elseif (preg_match('/^[\t ]+/', $input, $matches) === 1) {
            $move = $index + strlen($matches[0]);
        } else {
            throw new \Exception(
                sprintf(
                    'Failed to lex next token at position %d with lookahead: %s!',
                    $index,
                    var_export($input, true)
                )
            );
        }

        return [$move, $token];
    }
}
