<?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\CodeStructuralElement\Php;

use Magento\Mray\CodeStructuralElement\FullQualifiedStructuralElementName;
use Magento\Mray\CodeStructuralElement\StructuralElement;
use Magento\Mray\CodeStructuralElement\StructuralElementRegistry;
use Magento\Mray\CodeStructuralElement\StructuralElementRelation;
use function array_merge;
use function array_unique;
use function array_values;

class PhpStructuralElementRegistry implements StructuralElementRegistry
{
    /** @var array  */
    private $elements = [];
    /** @var array */
    private $aliases = [];

    /** @var array */
    private $relationsByInitiator = [];
    /** @var array  */
    private $relationsByInvolved = [];

    /**
     * @param StructuralElement $se
     */
    public function register(StructuralElement $se): void
    {
        $fqsen = $se->getFqsen();
        if (!$fqsen) {
            return; // ignore anonymous structural elements
        }
        $this->elements[(string)$fqsen] = $se;
    }

    /**
     * @return iterable
     */
    public function list(): iterable
    {
        foreach ($this->elements as $key => $element) {
            $fqsen = $this->aliases[$key] ?? $element->getFqsen();
            yield $fqsen => $element;
        }
        return;
        // phpcs:ignore Squiz.PHP.NonExecutableCode
        yield;
    }

    /**
     * @param FullQualifiedStructuralElementName $alias
     * @param StructuralElement $se
     */
    public function registerAlias(FullQualifiedStructuralElementName $alias, StructuralElement $se): void
    {
        if ((string)$alias === (string)$se->getFqsen()) {
            $this->register($se);
            return;
        }

        $this->elements[(string)$alias] = $se;
        $this->aliases[(string)$alias] = $alias;
    }

    /**
     * @param StructuralElementRelation $ser
     */
    public function registerRelation(StructuralElementRelation $ser): void
    {
        $this->relationsByInitiator[(string)$ser->getInitiator()][$ser->getId()] = $ser;
        $this->relationsByInvolved[(string)$ser->getInvolved()][$ser->getId()] = $ser;
    }

    /**
     * @param FullQualifiedStructuralElementName $fqsen
     * @return StructuralElement|null
     */
    public function find(FullQualifiedStructuralElementName $fqsen): ?StructuralElement
    {
        $se = $this->elements[(string)$fqsen] ?? null;
        if ($se) {
            return $se;
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function findRelations(FullQualifiedStructuralElementName $fqsen): array
    {
        return array_values(array_unique(array_merge(
            $this->findRelationsInitiatedBy($fqsen),
            $this->findRelationsInvolve($fqsen)
        )));
    }

    /**
     * @inheritDoc
     */
    public function findRelationsInitiatedBy(FullQualifiedStructuralElementName $fqsen): array
    {
        return $this->relationsByInitiator[(string)$fqsen] ?? [];
    }

    /**
     * @inheritDoc
     */
    public function findRelationsInvolve(FullQualifiedStructuralElementName $fqsen): array
    {
        return $this->relationsByInvolved[(string)$fqsen] ?? [];
    }
}
