<?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\ClassDeclaration;
use Magento\Mray\Index\Collection\Dictionary;
use Magento\Mray\Index\Data\PhpClassLikeMeta;
use function array_map;

class PhpClassMeta extends PhpClassLikeMeta implements ClassDeclaration
{
    /**
     * @param ClassDeclaration $d
     * @return static
     */
    public static function createFromObject(ClassDeclaration $d): self
    {
        $data = self::dataFromObject($d);

        $data['parent'] = $d->getParentClass();
        $data['implements'] = $d->getImplementedInterfaces();

        $data['flags'] = 0;
        if ($d->isAbstract()) {
            $data['flags'] |= PhpElementFlags::ABSTRACT;
        }
        if ($d->isFinal()) {
            $data['flags'] |= PhpElementFlags::FINAL;
        }

        return new static($data);
    }

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

    /**
     * @param array $packed
     * @param Dictionary $dict
     * @return array
     */
    protected static function unpackData(array $packed, Dictionary $dict): array
    {
        if (isset($packed[0])) {
            $packed[0] = self::unstringify($dict->read($packed[0]));
        }

        $implements = [];
        if (isset($packed[11])) {
            foreach ($packed[11] as $implement) {
                $implements[] = $dict->read($implement);
            }
        }

        $data = parent::unpackData($packed, $dict);
        $data['parent'] = isset($packed[10]) ? $dict->read($packed[10]) : null;
        $data['implements'] = $implements ?: null;
        $data['flags'] = $packed[12] ?? 0;
        return $data;
    }

    /**
     * @param Dictionary $dict
     * @return array
     */
    protected function packData(Dictionary $dict): array
    {
        $data = parent::packData($dict);

        // inheritance information moved to top level as contract described by members signatures
        $data[10] = isset($this->parent) ? $dict->write($this->parent) : null;
        if ($this->implements) {
            foreach ($this->implements as $implements) {
                $data[11][] = $dict->write($implements);
            }
        }

        $data[12] = $this->flags ?: null;

        $data[0] = $dict->write(self::stringify($data[0]));
        return $data;
    }

    /** @var string|null  */
    protected $parent;
    /** @var string[]|null  */
    protected $implements;
    /** @var int  */
    protected $flags;

    /**
     * @param array $data
     */
    public function __construct($data)
    {
        parent::__construct($data);
        $this->parent = $data['parent'] ?? null;
        $this->implements = isset($data['implements']) ? array_map(function (string $n) {
            return $n;
        }, $data['implements']): null;
        $this->flags = $data['flags'] ?? 0;
    }

    /**
     * @return string|null
     */
    public function getParentClass(): ?string
    {
        return $this->parent;
    }

    /**
     * @return string[]
     */
    public function getImplementedInterfaces(): array
    {
        return $this->implements;
    }

    /**
     * @inheritDoc
     */
    public function getImplementationProviders(): array
    {
        $providers = [];
        $parent = $this->getParentClass();
        if ($parent) {
            $providers[] = $parent;
        }
        foreach ($this->getImplementedInterfaces() as $i) {
            $providers[] = $i;
        }
        return $providers;
    }

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

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