<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Classes\Util;

use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Shopware\Components\Model\ModelEntity;

class Doctrine
{
    /**
     * Checks whether an associated entity exists or not. Because of doctrine's
     * lazy loading mechanism, this is less trivial than it sounds on first
     * sight.
     * If an associated entity is "lazy loaded", doctrine will create a proxy
     * object in place of the actual entity, when the "base" entity is loaded.
     * Only when the first call to the associated entity is made (like
     * $order->getPayment()), doctrine will fetch and load that object from the
     * database. The proxy object will be created no matter if the related
     * database item actually exists or not.
     * Furthermore in case of collection–based associations (X-to-many
     * associations), the collection will be kind of proxied as well and fully
     * loaded when it is first accessed.
     *
     * Usage example:
     * Check if the payment entity associated with an order entity
     * ($order instanceof Shopware\Models\Order\Order) exists:
     *
     *   Doctrine::associatedEntityExists($order->getPayment())
     *
     * Remark:
     * If no explicit fetch strategy is set for an association, associated
     * entities are loaded "directly" or "lazy" based on the association
     * type (one-to-one, ...) and the association direction (owning-side,
     * inverse-side):
     *
     *   - associated entities of the owning-side of an asscociation are
     *     lazy loaded by default
     *   - associated entities of the inverse-side of an association are
     *     loaded "directly" along with the "base" entity
     *
     * In case of a X-to-one association the "one"-side always represents the
     * inverse-side of an association, whereas in case of a many-to-many
     * association the programmer is in charge of choosing which side of the
     * association represents the owning- resp. inverse-side.
     *
     * EXTRA_LAZY loading is currently not supported by this method.
     *
     * @param mixed $entity    Current "state" of the associated entity as it is
     *                   accessible via the "base" entity, e.g. the value
     *                   returned by the getPayment() method of an order
     *                   model entity (Shopware\Models\Order\Order)
     * @return boolean
     */
    public static function associatedEntityExists($entity)
    {
        if (!$entity) {
            // The association has been loaded, but the related
            // entity (database item) does not exist
            return false;
        }

        if ($entity instanceof \Doctrine\ORM\Proxy\Proxy) {
            // The association is scheduled for lazy loading,
            // but has not been loaded yet. Try to load it in order
            // to check if the related entity (database item) exists
            try {
                $entity->__load();
            } catch (\Doctrine\ORM\EntityNotFoundException $e) {
                return false;
            }
        }

        if ($entity instanceof \Doctrine\Common\Collections\Collection) {
            // The association is collection-based (X-to-many association).
            // In this case the the collection will be fully loaded when it
            // is first accessed. Count() will implicitly trigger the loading
            // of the collection.
            if (count($entity) === 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Tests whether a query builder has a particular join for a root and a joined entity.
     *
     * @param QueryBuilder $builder
     * @param string $joinRoot
     * @param string $joinedEntity
     * @return bool true iff the passed in query builder has the specified join.
     */
    public static function builderHasJoin($builder, $joinRoot, $joinedEntity)
    {
        $joins = $builder->getDQLPart('join');

        if (!isset($joins[$joinRoot])) {
            return false;
        }
        $joinsForRoot = $joins[$joinRoot];

        /** @var Join $join */
        foreach ($joinsForRoot as $join) {
            if ($join->getJoin() === "${joinRoot}.${joinedEntity}") {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns true, iff the two given model instances share the same custom model namespace (e.g.
     * 'Shopware\CustomModels\MyPlugin'). That is, if the third component of their namespace is the same.
     *
     * @param ModelEntity $firstModelInstance
     * @param ModelEntity $secondModelInstance
     * @return bool
     */
    public static function customModelsShareNamespace(ModelEntity $firstModelInstance, ModelEntity $secondModelInstance)
    {
        $firstModelNamespace = ClassUtils::getClass($firstModelInstance);
        $secondModelNamespace = ClassUtils::getClass($secondModelInstance);
        $firstModelNamespaceParts = explode('\\', $firstModelNamespace);
        $secondModelNamespaceParts = explode('\\', $secondModelNamespace);

        return $firstModelNamespaceParts[2] === $secondModelNamespaceParts[2];
    }
}
