<?php

/**
 * MIT License. This file is part of the Propel package.
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Propel\Runtime\Connection;

use Propel\Runtime\Adapter\AdapterInterface;

/**
 * Manager for primary/replica connection to a datasource.
 */
class ConnectionManagerPrimaryReplica implements ConnectionManagerInterface
{
    /**
     * @var string The datasource name associated to this connection
     */
    protected $name;

    /**
     * @var array
     */
    protected $writeConfiguration = [];

    /**
     * @var \Propel\Runtime\Connection\ConnectionInterface|null
     */
    protected $writeConnection;

    /**
     * @var array
     */
    protected $readConfiguration = [];

    /**
     * @var \Propel\Runtime\Connection\ConnectionInterface|null
     */
    protected $readConnection;

    /**
     * @var bool Whether a call to getReadConnection() always returns a write connection.
     */
    protected $isForcePrimaryConnection = false;

    /**
     * @param string $name The datasource name associated to this connection
     */
    public function __construct(string $name)
    {
        $this->name = $name;
    }

    /**
     * @param string $name The datasource name associated to this connection
     *
     * @return void
     */
    #[\Override]
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    /**
     * @return string The datasource name associated to this connection
     */
    #[\Override]
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * For replication, whether to always force the use of a primary connection.
     *
     * @return bool
     */
    public function isForcePrimaryConnection(): bool
    {
        return $this->isForcePrimaryConnection;
    }

    /**
     * For replication, set whether to always force the use of a primary connection.
     *
     * @param bool $isForceMasterConnection
     *
     * @return void
     */
    public function setForcePrimaryConnection(bool $isForceMasterConnection): void
    {
        $this->isForcePrimaryConnection = $isForceMasterConnection;
    }

    /**
     * Set the configuration for the master (write) connection.
     *
     * <code>
     * $manager->setWriteConfiguration(array(
     *   'dsn' => 'mysql:dbname=test_master',
     *   'user' => 'mywriteuser',
     *   'password' => 'S3cr3t'
     * ));
     * </code>
     *
     * @param array $configuration
     *
     * @return void
     */
    public function setWriteConfiguration(array $configuration): void
    {
        $this->writeConfiguration = $configuration;
        $this->closeConnections();
    }

    /**
     * Set the configuration for the replica (read) connections.
     *
     * <code>
     * $manager->setReadConfiguration(array(
     *   array(
     *     'dsn' => 'mysql:dbname=test_replica1',
     *     'user' => 'myreaduser',
     *     'password' => 'F00baR'
     *   ),
     *   array(
     *     'dsn' => 'mysql:dbname=test_replica2',
     *     'user' => 'myreaduser',
     *     'password' => 'F00baR'
     *   )
     * ));
     * </code>
     *
     * @param array $configuration
     *
     * @return void
     */
    public function setReadConfiguration(array $configuration): void
    {
        $this->readConfiguration = $configuration;
        $this->closeConnections();
    }

    /**
     * Get a master connection.
     *
     * If no master connection exist yet, open it using the write configuration.
     *
     * @param \Propel\Runtime\Adapter\AdapterInterface|null $adapter
     *
     * @return \Propel\Runtime\Connection\ConnectionInterface
     */
    #[\Override]
    public function getWriteConnection(?AdapterInterface $adapter = null): ConnectionInterface
    {
        if ($this->writeConnection === null) {
            $this->writeConnection = ConnectionFactory::create($this->writeConfiguration, $adapter);
            $this->writeConnection->setName($this->getName());
        }

        return $this->writeConnection;
    }

    /**
     * Get a replica connection.
     *
     * If no replica connection exist yet, choose one configuration randomly in the
     * read configuration to open it.
     *
     * @param \Propel\Runtime\Adapter\AdapterInterface|null $adapter
     *
     * @return \Propel\Runtime\Connection\ConnectionInterface
     */
    #[\Override]
    public function getReadConnection(?AdapterInterface $adapter = null): ConnectionInterface
    {
        if ($this->writeConnection && $this->writeConnection->inTransaction()) {
            return $this->writeConnection;
        }

        if ($this->isForcePrimaryConnection()) {
            return $this->getWriteConnection($adapter);
        }
        if ($this->readConnection === null) {
            if (!$this->readConfiguration) {
                $this->readConnection = $this->getWriteConnection($adapter);
            } else {
                $keys = array_keys($this->readConfiguration);
                $key = $keys[mt_rand(0, count($keys) - 1)];
                $configuration = $this->readConfiguration[$key];
                $this->readConnection = ConnectionFactory::create($configuration, $adapter);
                $this->readConnection->setName($this->getName());
            }
        }

        return $this->readConnection;
    }

    /**
     * @return void
     */
    #[\Override]
    public function closeConnections(): void
    {
        $this->writeConnection = null;
        $this->readConnection = null;
    }
}
