<?php

namespace FiloBlu\Flow\Model\Connector;

use Exception;
use FiloBlu\Flow\Helper\LoggerProvider;
use Monolog\Logger;
use RuntimeException;

/**
 * Created by PhpStorm.
 * User: marcovalenziano
 * Date: 07/05/15
 * Time: 12:40
 */
class Ftp extends AbstractConnector
{
    /**
     * @var string
     */
    public $type = 'FTP';
    /**
     * @var string
     */
    public $ftpDir;
    /**
     * @var string
     */
    public $incomingDir;
    /**
     * @var string
     */
    public $localDir;
    /**
     * @var string
     */
    public $localDirOriginal;
    /**
     * @var string
     */
    public $destinationDir;
    /**
     * @var string
     */
    public $dirRoot;
    /**
     * @var string
     */
    protected $host;
    /**
     * @var string
     */
    protected $username;
    /**
     * @var string
     */
    protected $password;
    /**
     * @var int
     */
    protected $passive = 0;
    /**
     * @var \FiloBlu\Flow\Helper\Monolog\Logger|Logger
     */
    protected $_logger;

    /**
     * Init FTP Class retrieving configuration.
     *
     * @param LoggerProvider $_loggerProvider
     */
    public function __construct(
        LoggerProvider $_loggerProvider
    ) {
        parent::__construct($_loggerProvider);
        $this->_logger = $_loggerProvider->getLogger();
    }

    /**
     * @param $config
     */
    public function setConfig($config)
    {
        $this->_config = $config;

        $this->host = $this->_config['host'];
        $this->username = $this->_config['username'];
        $this->password = $this->_config['password'];
        $this->passive = $this->_config['passive'];
        $this->ftpDir = $this->_config['ftp_dir'];

        if (isset($this->_config['incomingDir'])) {
            $this->incomingDir = $this->_config['incomingDir'];
        }

        //TODO: Create a provider
        $this->localDir = BP . '/var/' . $this->_config['local_dir'];
        $this->localDirOriginal = $this->_config['local_dir'];

        $this->destinationDir = '';
        if (isset($this->_config['destination_dir'])) {
            $this->destinationDir = $this->_config['destination_dir'];
        }

        $this->dirRoot = BP;
        $this->connection = null;

        $this->_mask = (string)$this->_config['regex'];

        if (!file_exists($this->localDir) && !mkdir($concurrentDirectory = $this->localDir, 0755, true) && !is_dir(
                $concurrentDirectory
            )) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }
    }

    /**
     * @return mixed|void
     */
    public function fetchResources()
    {
    }

    /**
     * @return mixed
     */
    public function getLocalDir()
    {
        return $this->localDir;
    }

    /**
     * @param $dir
     * @return $this
     */
    public function setLocalDir($dir)
    {
        $this->localDir = $dir;
        return $this;
    }

    /**
     * List, Download and Delete resources from ftp.
     *
     * @return array|mixed
     * @throws Exception
     */
    public function getResources()
    {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection();
        $this->login($this->username, $this->password);

        $resources = $this->listDir($this->ftpDir);
        $downloadedFiles = [];

        foreach ($resources as $resource) {
            $baseFile = basename($resource);

            // la regex deve avere caratteri delimiter! nel campo backend
            if (preg_match($this->_mask, $baseFile)) {
                $downloadedFiles[] = $baseFile;
            }
        }

        $this->close();
        return $downloadedFiles;
    }

    /**
     * @return mixed
     * @throws Exception
     */
    public function getConnection()
    {
        $this->openConnection();
        return $this->connection;
    }

    /**
     * @return resource
     * @throws Exception
     */
    public function openConnection()
    {
        $host = $this->host;

        if (!$this->connection) {
            if (!$host) {
                $this->_logger->info(__METHOD__ . ' -> Missing HOST');
                throw new Exception('Missing HOST');
            }

            $this->connection = ftp_connect($host);
            if (!$this->connection) {
                $this->_logger->info(__METHOD__ . ' -> FTP connection failed');
                throw new Exception('FTP connection failed');
            }
            $this->login($this->username, $this->password);
        }
        return $this->connection;
    }

    /**
     * @param string $user
     * @param string $pass
     * @throws Exception
     */
    public function login($user = null, $pass = null)
    {
        $login_result = ftp_login($this->connection, $user, $pass);

        if (!$login_result) {
            $this->_logger->info(__METHOD__ . ' -> FTP login failed');
            throw new Exception('FTP login failed');
        }

        ftp_pasv($this->connection, $this->passive);
    }

    /**
     * @param $dir
     * @return array
     */
    public function listDir($dir)
    {
        $list = [];

        $this->_logger->info(__METHOD__);

        $connection = $this->connection;

        ftp_pasv($connection, true);

        putenv('TMPDIR=' . getenv('TMPDIR'));

        if (ftp_chdir($connection, $dir)) {
            $list = $this->getOnlyFiles('.');
        }

        return $list;
    }

    /**
     * @param $path
     * @return array
     */
    public function getOnlyFiles($path)
    {
        $files = [];
        $contents = ftp_rawlist($this->connection, $path);

        foreach ($contents as $line) {
            $info = preg_split('/[\s]+/', $line, 9);

            /* List all files excluding directories */
            if ($info[0] !== 'total' && strpos($info[0], '-') === 0) {
                $files[] = $info[8];
            }
        }

        return $files;
    }

    /**
     * @return bool
     */
    public function close()
    {
        $closed = ftp_close($this->connection);
        $this->connection = null;
        return $closed;
    }

    /**
     * @param $resource
     * @param null $copy
     * @param int $ftp_mode
     * @param bool $keep_conn_open
     * @param bool $unique_filename_pattern
     * @return array|string|string[]|null
     * @throws Exception
     */
    public function receiveResource(
        $resource,
        $copy = null,
        $ftp_mode = FTP_ASCII,
        $keep_conn_open = false,
        $unique_filename_pattern = false
    ) {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection();

        $downloadedFile = [];

        if (!is_dir($this->localDir) && trim($this->localDir) !== '' && !mkdir(
                $concurrentDirectory = $this->localDir,
                0777,
                true
            ) && !is_dir($concurrentDirectory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }

        $basefile = basename($resource);
        if ($unique_filename_pattern) {
            $basefile = $this->fixFilename($basefile, $unique_filename_pattern);
        }

        $localFile = $this->localDir . '/' . $basefile;

        if (file_exists($localFile)) {
            $this->_logger->info('FTP - file ' . $basefile . ' already exists');
            throw new RuntimeException('FTP - file ' . $basefile . ' already exists');
        }

        try {
            if ($this->downloadFile($localFile, $resource, $ftp_mode)) {
                $downloadedFile = $basefile;
                $this->deleteFile($resource);
// Copy to ite is moved to fetch
//                if ($copy) {
//                    $fileInfo = pathinfo($localFile);
//                    $fileCopyName = $copy . '.' . $fileInfo['extension'];
//                    $fileCopy = $this->localDir . '/' . $fileCopyName;
//                    if (copy($localFile, $fileCopy)) {
//                        $downloadedFile = ['basefile' => $downloadedFile, 'copy' => $fileCopyName];
//                    }
//                }
            }
        } catch (Exception $e) {
            $this->_logger->info($e->getMessage());
        }

        $this->close();

        return $downloadedFile;
    }

    /**
     * This will change the filename to an unique filename appending the unique pattern before extension
     * @param $fileName
     * @param null $unique_filename_pattern
     * @return string|string[]|null
     */
    public function fixFilename($fileName, $unique_filename_pattern = null)
    {
        $fileName = preg_replace('/[^a-z0-9_ \\-\\.]+/i', '', $fileName);

        // Add unique identifier to the filename
        if (!$unique_filename_pattern) {
            return $fileName;
        }

        // No extension? then return it with the unique identifier
        $parts = explode('.', $fileName);
        if (count($parts) === 1) {
            return $fileName . $unique_filename_pattern;
        }

        // Have extension, then add unique identifier before extension
        $last = array_pop($parts);
        $parts = [implode('.', $parts), $last];

        return sprintf('%s%s.%s', $parts[0], $unique_filename_pattern, $parts[1]);
    }

    /**
     * Scarica un file dall'FTP
     *
     * @param $local_file
     * @param $server_file
     * @param int $mode
     * @return bool
     * @throws Exception
     */
    public function downloadFile($local_file, $server_file, $mode = FTP_ASCII)
    {
        $this->_logger->info(__METHOD__);

        $connection = $this->connection;

        if (!ftp_chdir($connection, $this->ftpDir)) {
            $this->_logger->info(
                __METHOD__ . "FTP HELPER: function ftp_chdir failed - downloading {$server_file} > {$local_file}"
            );
            throw new Exception("FTP HELPER: function ftp_chdir failed - downloading {$server_file} > {$local_file}");
        }

        $res = ftp_get($connection, $local_file, $server_file, $mode);
        if (!$res) {
            $this->_logger->info(
                __METHOD__ . "FTP HELPER: function ftp_get failed - downloading {$server_file} > {$local_file}"
            );
            throw new Exception("FTP HELPER: function ftp_get failed - downloading {$server_file} > {$local_file}");
        }

        chmod($local_file, 0777);
        return true;
    }

    /**
     *
     * @param string $serverFilename
     * @return bool
     */
    public function deleteFile($serverFilename)
    {
        $this->_logger->info(__METHOD__);

//        ftp_chdir($this->connection, $this->ftpDir);

        return ftp_delete($this->connection, $serverFilename);
    }

    /**
     *
     *
     */
    public function isReceived()
    {
    }

    /**
     * @param $resource
     * @return bool
     * @throws Exception
     */
    public function sendResource($resource)
    {
        $this->_logger->info(__METHOD__);

        $this->connection = $this->getConnection($this->host);
        $this->login($this->username, $this->password);

        if (!is_dir($this->localDir) && trim($this->localDir) !== '' && !mkdir(
                $concurrentDirectory = $this->localDir,
                0777,
                true
            ) && !is_dir($concurrentDirectory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }

        $baseFile = basename($resource);
        $localFile = $this->localDir . DIRECTORY_SEPARATOR . $baseFile;
        $remoteFile = $this->ftpDir . DIRECTORY_SEPARATOR . $baseFile;

        if (file_exists($localFile) && $this->uploadFile($remoteFile, $localFile)) {
            unlink($localFile);
            return true;
        }
        return false;
    }

    /**
     * @param $remote_file
     * @param $xmlPath
     * @param int $mode
     * @return bool
     * @throws Exception
     */
    public function uploadFile($remote_file, $local_file, $mode = FTP_ASCII)
    {
        $connection = $this->connection;

        $this->_logger->info("Upload del file {$local_file} in {$remote_file}");

        $res = ftp_put($connection, $remote_file, $local_file, $mode); // FTP_ASCII per i file di testo in generale

        if (!$res) {
            $this->_logger->info("FTP HELPER: upload failed {$local_file} > {$remote_file}");
            throw new Exception("FTP HELPER: upload failed {$local_file} > {$remote_file}");
        }

        $this->_logger->info("FTP HELPER: Uploaded {$local_file} > {$remote_file}");
        return true;
    }

    /**
     * @param $dir
     * @return $this
     */
    public function setFtpDir($dir)
    {
        $this->ftpDir = $dir;
        return $this;
    }
}
