<?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 Sut\Domain\Etalon;

use Sut\Domain\Etalon\DTO\ModificationCheckResult;
use Sut\Domain\Issue\IssueFactory;

class GetModifiedFiles
{
    /**
     * @var HashFileInterface
     */
    private $hasher;

    /**
     * @var GetIterator
     */
    private $etalon;

    /**
     * @var IssueFactory
     */
    private $issueFactory;

    /**
     * @var GetUpdatedPackages
     */
    private $getUpdatedPackages;

    /**
     * @var array
     */
    private $removedDirectories;

    /**
     * @param HashFileInterface $hasher
     * @param GetIterator $etalon
     * @param GetUpdatedPackages $getUpdatedPackages
     * @param IssueFactory $issueFactory
     */
    public function __construct(
        HashFileInterface $hasher,
        GetIterator $etalon,
        GetUpdatedPackages $getUpdatedPackages,
        IssueFactory $issueFactory
    ) {
        $this->hasher = $hasher;
        $this->etalon = $etalon;
        $this->getUpdatedPackages = $getUpdatedPackages;
        $this->issueFactory = $issueFactory;
        $this->removedDirectories = [];
    }

    /**
     * Execute compares the same files in two different folders and check if they are different by
     * comparing their hash signatures. If these signatures are different, it means that the file
     * in $path has been modified.
     *
     * @param string $path Path to the project to check
     * @param string $etalonPath Path to the etalon folder to compare against
     * @return ModificationCheckResult Object with modified files information
     */
    public function execute(string $path, string $etalonPath): ModificationCheckResult
    {
        $issues = $this->getUpdatedPackages->execute($path, $etalonPath);
        $iterator = $this->etalon->execute($etalonPath, array_keys($issues));
        $checkedFiles = 0;
        $modifiedFiles = 0;
        // We normalize path adding a trailing slash in case it is missing.
        $path = rtrim($path, '/') . '/';

        foreach ($iterator as $etalonCurrentFile => $etalonCurrentHash) {
            if ($this->isInRemovedDirectory($path . $etalonCurrentFile)) {
                continue;
            }
            $isDirectory = $etalonCurrentHash === null;

            if (!file_exists($path . $etalonCurrentFile)) {
                $checkedFiles++;
                if ($isDirectory) {
                    $issues[] = $this->issueFactory->createIssue(
                        2005,
                        [$etalonCurrentFile]
                    );
                    $modifiedFiles++;
                    $this->removedDirectories[] = $path . $etalonCurrentFile;
                    continue;
                }
                $issues[] = $this->issueFactory->createIssue(
                    2001,
                    [$etalonCurrentFile]
                );
                $modifiedFiles++;
                continue;
            }
            if ($isDirectory) {
                continue;
            }
            $checkedFiles++;
            if ($etalonCurrentHash !== $this->hasher->execute($path . $etalonCurrentFile)) {
                $issues[] = $this->issueFactory->createIssue(
                    2002,
                    [$etalonCurrentFile]
                );
                $modifiedFiles++;
            }
        }
        return new ModificationCheckResult(array_values($issues), $checkedFiles, $modifiedFiles);
    }

    /**
     * @param string $file
     * @return bool
     */
    private function isInRemovedDirectory(string $file): bool
    {
        foreach ($this->removedDirectories as $removedDirectory) {
            if (strpos($file, $removedDirectory) === 0) {
                return true;
            }
        }
        return false;
    }
}
