<?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\Components\FileStorage;

use League\Flysystem\FileNotFoundException;
use League\Flysystem\FilesystemInterface;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\FileSystemExceptions\FileNotReadableException;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\FileSystemExceptions\FileNotStreamableException;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\FileSystemExceptions\FileNotWritableException;
use Shopware\Plugins\ViisonCommon\Classes\FileResponseStream;

class FlysystemFileStorage implements FileStorage
{
    /**
     * @var FilesystemInterface
     */
    private $filesystem;

    /**
     * The directory in $this->filesystem to read and write the files from/to.
     *
     * @var string|null
     */
    private $path;

    /**
     * @param FilesystemInterface $fileSystem
     * @param string|null $path a path to a directory in $fileSystem that is used for reading and writing files from/to
     */
    public function __construct(FilesystemInterface $fileSystem, $path = null)
    {
        $this->filesystem = $fileSystem;
        $this->path = $path;
    }

    /**
     * @inheritDoc
     */
    public function readFileContents($fileName)
    {
        $filesystemFilePath = $this->getFilesystemFilePath($fileName);
        try {
            $contents = $this->filesystem->read($filesystemFilePath);

            if ($contents === false) {
                throw new FileNotReadableException($filesystemFilePath);
            }

            return $contents;
        } catch (FileNotFoundException $e) {
            throw new FileNotReadableException($filesystemFilePath, $e);
        }
    }

    /**
     * @inheritDoc
     */
    public function writeFileContents($fileName, $contents)
    {
        $filesystemFilePath = $this->getFilesystemFilePath($fileName);
        $success = null;
        if ($this->filesystem->has($filesystemFilePath)) {
            $success = $this->filesystem->update($filesystemFilePath, $contents);
        } else {
            $success = $this->filesystem->write($filesystemFilePath, $contents);
        }

        if ($success === false) {
            throw new FileNotWritableException($filesystemFilePath);
        }
    }

    /**
     * @inheritdoc
     */
    public function streamFile($fileName, FileResponseStream $fileResponseStream)
    {
        $filesystemFilePath = $this->getFilesystemFilePath($fileName);
        $fileHandle = null;
        try {
            $fileHandle = $this->filesystem->readStream($filesystemFilePath);
        } catch (FileNotFoundException $e) {
            throw new FileNotReadableException($filesystemFilePath, $e);
        }

        if ($fileHandle === false) {
            throw new FileNotStreamableException($filesystemFilePath);
        }

        $chunkSizeInBytes = 1024 * 1024;
        try {
            while (!feof($fileHandle)) {
                $nextChunkOfData = fgets($fileHandle, $chunkSizeInBytes);
                $fileResponseStream->write($nextChunkOfData);
            }
        } catch (\Exception $e) {
            throw new FileNotStreamableException($filesystemFilePath, $e);
        } finally {
            $fileResponseStream->close();
            fclose($fileHandle);
        }
    }

    /**
     * @inheritDoc
     */
    public function deleteFile($fileName)
    {
        $filesystemFilePath = $this->getFilesystemFilePath($fileName);

        try {
            $success = $this->filesystem->delete($filesystemFilePath);

            if (!$success) {
                throw new FileNotWritableException(dirname($filesystemFilePath));
            }
        } catch (FileNotFoundException $e) {
            throw new FileNotReadableException($filesystemFilePath, $e);
        }
    }

    /**
     * @inheritDoc
     */
    public function doesFileExist($fileName)
    {
        return $this->filesystem->has($this->getFilesystemFilePath($fileName));
    }

    /**
     * Gets the full path for the file $fileName in the filesystem $this->filesystem taking $this->directory into
     * account.
     *
     * @param string $fileName
     * @return string
     */
    private function getFilesystemFilePath($fileName)
    {
        $filesystemFilePath = '';
        if ($this->path !== null && $this->path !== '') {
            $filesystemFilePath .= rtrim($this->path, '/\\') . '/';
        }

        return $filesystemFilePath . $fileName;
    }
}
