<?php
/**
 * Copyright 2020 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Magento\Mray\Index;

use Magento\Mray\CodeStructuralElement\Php\Reflection\MethodDeclaration;
use Magento\Mray\Index\Collection\Dictionary;
use Magento\Mray\Index\Data\PhpClassLikeMemberMeta;
use Magento\Mray\Index\Data\PhpParameterMeta;
use Magento\Mray\Index\Data\PhpTypeMeta;
use phpDocumentor\Reflection\Type;
use function array_map;
use function is_array;
use function strtolower;

class PhpMethodMeta extends PhpClassLikeMemberMeta implements MethodDeclaration
{
    /**
     * @var bool[]
     */
    private static $magicNames = [
        '__construct'  => true,
        '__destruct'   => true,
        '__call'       => true,
        '__callstatic' => true,
        '__get'        => true,
        '__set'        => true,
        '__isset'      => true,
        '__unset'      => true,
        '__sleep'      => true,
        '__wakeup'     => true,
        '__tostring'   => true,
        '__set_state'  => true,
        '__clone'      => true,
        '__invoke'     => true,
        '__debuginfo'  => true,
    ];

    /**
     * @param MethodDeclaration $d
     * @return static
     */
    public static function createFromObject(MethodDeclaration $d): self
    {
        $data = self::membershipDataFromObject($d);

        if ($d->isFinal()) {
            $data['flags'] |= PhpElementFlags::FINAL;
        }
        if ($d->isAbstract()) {
            $data['flags'] |= PhpElementFlags::ABSTRACT;
        }
        if ($d->isStatic()) {
            $data['flags'] |= PhpElementFlags::STATIC;
        }
        $data['returns'] = PhpTypeMeta::createFromObject($d->getReturnType());
        foreach ($d->getParams() as $param) {
            $data['params'][] = PhpParameterMeta::createFromObject($param);
        }

        return new static($data);
    }

    /**
     * @return int
     */
    public static function getKind(): int
    {
        return PhpElementKinds::DECLARED_ | PhpElementKinds::MEMBER_ | PhpElementKinds::CALLABLE_;
    }

    /** @var PhpTypeMeta  */
    private $returns;
    /** @var PhpParameterMeta[]|null */
    protected $params;

    /**
     * @param array $packed
     * @param Dictionary $dict
     * @return array
     */
    protected static function unpackData(array $packed, Dictionary $dict): array
    {
        $returns = null;
        $params = [];
        if (isset($packed[0])) {
            $packed[0] = self::unstringify($dict->read($packed[0]));
            $returns = isset($packed[0][10]) ? $dict->read($packed[0][10]) : null;
            if ($returns) {
                $returns = PhpTypeMeta::unpack($returns, $dict);
            }
            if (isset($packed[0][11]) && is_array($packed[0][11])) {
                foreach ($packed[0][11] as $param) {
                    $params[] = PhpParameterMeta::unpack($param, $dict);
                }
            }
        }
        $data = parent::unpackData($packed, $dict);
        $data['returns'] = $returns;
        $data['params'] = $params;
        return $data;
    }

    /**
     * @param Dictionary $dict
     * @return array
     */
    protected function packData(Dictionary $dict): array
    {
        $data = parent::packData($dict);
        $data[0][10] = $dict->write($this->returns->pack($dict));
        if ($this->params) {
            foreach ($this->params as $param) {
                $data[0][11][] = $param->pack($dict);
            }
        }
        $data[0] = $dict->write(self::stringify($data[0]));
        return $data;
    }

    /**
     * @param array $data
     */
    public function __construct($data)
    {
        parent::__construct($data);
        $this->returns = $data['returns'] ?? null;
        $this->params = isset($data['params']) ? array_map(function (PhpParameterMeta $param) {
            return $param;
        }, $data['params']) : null;
    }

    /**
     * @return Type|null
     */
    public function getValueType(): ?Type
    {
        return $this->getReturnType();
    }

    /**
     * @return Type|null
     */
    public function getReturnType(): ?Type
    {
        if ($this->returns) {
            return $this->returns->cast();
        }
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getParams(): array
    {
        return $this->params ?? [];
    }

    /**
     * @return bool
     */
    public function isAbstract(): bool
    {
        return (bool)($this->flags & PhpElementFlags::ABSTRACT);
    }

    /**
     * @return bool
     */
    public function isFinal(): bool
    {
        return (bool)($this->flags & PhpElementFlags::FINAL);
    }

    /**
     * @return bool
     */
    public function isStatic(): bool
    {
        return (bool)($this->flags & PhpElementFlags::STATIC);
    }

    /**
     * @return bool
     */
    public function isMagic(): bool
    {
        return isset(self::$magicNames[strtolower($this->getName())]);
    }
}
