<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Framework\DB;

use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\DataConverter\DataConversionException;
use Magento\Framework\DB\DataConverter\DataConverterInterface;
use Magento\Framework\DB\Query\Generator;
use Magento\Framework\DB\Select\QueryModifierInterface;

/**
 * Convert field data from one representation to another
 */
class FieldDataConverter
{
    /**
     * Batch size env variable name
     */
    const BATCH_SIZE_VARIABLE_NAME = 'DATA_CONVERTER_BATCH_SIZE';

    /**
     * Default batch size
     */
    const DEFAULT_BATCH_SIZE = 50000;

    /**
     * @var Generator
     */
    private $queryGenerator;

    /**
     * @var DataConverterInterface
     */
    private $dataConverter;

    /**
     * @var SelectFactory
     */
    private $selectFactory;

    /**
     * @var string|null
     */
    private $envBatchSize;

    /**
     * Constructor
     *
     * @param Generator $queryGenerator
     * @param DataConverterInterface $dataConverter
     * @param SelectFactory $selectFactory
     * @param string|null $envBatchSize
     */
    public function __construct(
        Generator $queryGenerator,
        DataConverterInterface $dataConverter,
        SelectFactory $selectFactory,
        $envBatchSize = null
    ) {
        $this->queryGenerator = $queryGenerator;
        $this->dataConverter = $dataConverter;
        $this->selectFactory = $selectFactory;
        $this->envBatchSize = $envBatchSize;
    }

    /**
     * Convert table field data from one representation to another
     *
     * @param AdapterInterface $connection
     * @param string $table
     * @param string $identifier
     * @param string $field
     * @param QueryModifierInterface|null $queryModifier
     * @throws FieldDataConversionException
     * @return void
     */
    public function convert(
        AdapterInterface $connection,
        $table,
        $identifier,
        $field,
        QueryModifierInterface $queryModifier = null
    ) {
        $select = $this->selectFactory->create($connection)
            ->from($table, [$identifier, $field])
            ->where($field . ' IS NOT NULL');
        if ($queryModifier) {
            $queryModifier->modify($select);
        }
        $iterator = $this->queryGenerator->generate($identifier, $select, $this->getBatchSize());
        foreach ($iterator as $selectByRange) {
            $rows = $connection->fetchPairs($selectByRange);
            $uniqueFieldDataArray = array_unique($rows);
            foreach ($uniqueFieldDataArray as $uniqueFieldData) {
                $ids = array_keys($rows, $uniqueFieldData);
                try {
                    $convertedValue = $this->dataConverter->convert($uniqueFieldData);
                    if ($uniqueFieldData === $convertedValue) {
                        // Skip for data rows that have been already converted
                        continue;
                    }
                    $bind = [$field => $convertedValue];
                    $where = [$identifier . ' IN (?)' => $ids];
                    $connection->update($table, $bind, $where);
                } catch (DataConversionException $e) {
                    throw new \Magento\Framework\DB\FieldDataConversionException(
                        sprintf(
                            \Magento\Framework\DB\FieldDataConversionException::MESSAGE_PATTERN,
                            $field,
                            $table,
                            $identifier,
                            implode(', ', $ids),
                            get_class($this->dataConverter),
                            $e->getMessage()
                        )
                    );
                }
            }
        }
    }

    /**
     * Get batch size from environment variable or default
     *
     * @return int
     */
    private function getBatchSize()
    {
        if (null !== $this->envBatchSize) {
            $batchSize = (int) $this->envBatchSize;
            if (bccomp($this->envBatchSize, PHP_INT_MAX, 0) === 1 || $batchSize < 1) {
                throw new \InvalidArgumentException(
                    'Invalid value for environment variable ' . self::BATCH_SIZE_VARIABLE_NAME . '. '
                    . 'Should be integer, >= 1 and < value of PHP_INT_MAX'
                );
            }
            return $batchSize;
        }
        return self::DEFAULT_BATCH_SIZE;
    }
}
