<?php

namespace FiloBlu\Flow\Model\Channel\In;

use Exception;
use FiloBlu\Flow\Helper\LoggerProvider;
use FiloBlu\Flow\Model\Channel\Config;
use FiloBlu\Flow\Model\Channel\ConfigFactory;
use FiloBlu\Flow\Model\Connector\AbstractConnector;
use FiloBlu\Flow\Model\Map;
use FiloBlu\Flow\Model\Parser\AbstractParser;
use Magento\Framework\ObjectManagerInterface;
use Monolog\Logger;

use function count;

/**
 * Class AbstractModel
 * @package FiloBlu\Flow\Model\Channel\In
 */
abstract class AbstractModel
{
    /**
     * @var Map $map
     */
    public $map;

    /**
     * @var
     */
    protected $_name;

    /**
     * @var
     */
    protected $_data;

    /**
     * @var AbstractConnector
     */
    protected $_connector;

    /**
     * @var
     */
    protected $_parser;

    /**
     * @var
     */
    protected $_config;

    /**
     * @var ConfigFactory
     */
    protected $channelConfigFactory;

    /**
     * @var
     */
    protected $_file;

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

    /**
     * @var Logger
     */
    protected $_logger;

    /**
     * @var ObjectManagerInterface
     */
    protected $objectManager;

    /**
     * @var Config
     */
    protected $_configModel;

    /**
     * AbstractModel constructor.
     * @param LoggerProvider $loggerProvider
     * @param ConfigFactory $channelConfigFactory
     * @param ObjectManagerInterface $objectManager
     */
    public function __construct(
        LoggerProvider $loggerProvider,
        ConfigFactory $channelConfigFactory,
        ObjectManagerInterface $objectManager
    ) {
        $this->_logger = $loggerProvider->getLogger();
        $this->objectManager = $objectManager;
        $this->channelConfigFactory = $channelConfigFactory;
    }

    /**
     * @param $data
     * @return bool
     */
    public function insertData($data)
    {
        $model = $this->objectManager->create('FiloBlu\Flow\Model\\' . $this->getConfig('flow'));

        if ($this->_configModel->usesMap()) {
            foreach ($data as $field => $value) {
                $fieldMap = $this->map->get($field);

                if (isset($fieldMap['skip']) && $fieldMap['skip']) {
                    continue;
                }

                $model->setData($fieldMap['to'], $value);
            }
        } else {
                $model->setData($data);
        }

        $model->setMetaFile($this->getFile()->getId());
        $model->setMetaInsertTime(date('Y-m-d H:i:s'));
        $model->setMetaProcessed(0);

        return $model->save();
    }

    /**
     * @param $key
     * @return string|null
     */
    public function getConfig($key)
    {
        return $this->_config[$key] ?? null;
    }

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

    /**
     * @param $file
     * @return $this
     */
    public function setFile($file)
    {
        $this->_file = $file;
        return $this;
    }

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

    /**
     * @param $name
     */
    public function setName($name)
    {
        $this->_name = $name;
    }

    /**
     * @param $channelName
     * @return $this
     * @throws Exception
     */
    public function load($channelName)
    {
        $this->init($channelName);
        return $this;
    }

    /**
     * @param $channelName
     * @throws Exception
     */
    public function init($channelName)
    {
        $this->loadConfig($channelName);
    }

    /**
     * @param $name
     * @return bool
     * @throws Exception
     */
    public function loadConfig($name)
    {
        $this->_configModel = $this->channelConfigFactory->create()->load($name);
        $this->_config = $this->_configModel->get('config');
        $this->setConnector($this->_configModel->get('connector'));
        $this->setParser($this->_configModel->get('parser'));
        $this->setName($name);
        return true;
    }

    /**
     * @return AbstractConnector
     */
    public function getConnector()
    {
        return $this->_connector;
    }

    /**
     * @param $connector
     * @throws Exception
     */
    public function setConnector($connector)
    {
        $connectorType = sprintf('FiloBlu\Flow\Model\Connector\%s', ucfirst(trim($connector['type'])));
        $connectorClass = $this->objectManager->create($connectorType);

        if (!$connectorClass instanceof AbstractConnector) {
            throw new Exception(
                sprintf("Error in parser config. '%s' is not an instance of '%s'",
                    $connectorType,
                    AbstractConnector::class)
            );
        }

        $this->_connector = $connectorClass;
        $this->_connector->setConfig($connector);
    }

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

    /**
     * @param $parser
     * @throws Exception
     */
    public function setParser($parser)
    {
        $parserType = sprintf('FiloBlu\Flow\Model\Parser\%s', ucfirst(trim($parser['type'])));
        $this->_parser = $this->objectManager->create($parserType);

        if (!($this->_parser instanceof AbstractParser)) {
            throw new Exception(sprintf(
                "Error in parser config. '%s' is not an instance of '%s'",
                $parserType,
                AbstractParser::class
            ));
        }

        $this->_parser->setConfig($parser);
    }

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

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

    /**
     * @return bool
     * @throws Exception
     */
    public function rlist()
    {
        if (!$this->_connector) {
            throw new Exception('no connector joined to channel');
        }

        if ($this->getData('depends_folder')) {
            $depends_resources = $this->_connector->listDir($this->getData('depends_folder'));
            if (count($depends_resources) > 0) {
                return false;
            }
        }

        if ($this->getData('depends_file')) {
            $depends_resources = $this->_connector->getResources();
            if (count($depends_resources) > 0) {
                return false;
            }
        }

        $resources = $this->_connector->getResources();

        if (!count($resources)) {
            return false;
        }

        return $resources;
    }

    /**
     * @param $key
     * @return null
     */
    public function getData($key)
    {
        return $this->_data[$key] ?? null;
    }

    /**
     * @param $file
     * @return mixed
     * @throws Exception
     */
    public function receiveFile($file)
    {
        if (!$this->_connector) {
            throw new Exception('No connector joined to channel');
        }

        return $this->_connector->receiveResource($file->getName());
    }

    /**
     * @param $file
     * @return bool
     * @throws Exception
     */
    public function parse($file)
    {
        if (!$this->_connector) {
            throw new Exception('no connector joined to channel');
        }

        if (!$this->_parser) {
            throw new Exception('no parser joined to channel');
        }

        $this->setFile($file);
        $localDir = $this->_connector->getLocalDir();

        $this->_parser->loadMap($file->getChannel());
        $this->setMap($this->_parser->map);
        $this->_parser->setChannel($this);

        //Il Parser chiama il channel con il metodo insertData

        $this->_parser->process($localDir . DIRECTORY_SEPARATOR . $file->getName());

        return true;
    }

    /**
     * @param Map $map
     *
     * @return $this
     */
    public function setMap($map)
    {
        $this->map = $map;
        return $this;
    }

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

    /**
     * @return bool
     */
    public function commitData()
    {
        return true;
    }

    /**
     * @return Config
     */
    public function getChannelConfig()
    {
        return $this->_configModel;
    }

    /**
     * @return \FiloBlu\Flow\Helper\Monolog\Logger|Logger
     */
    public function getLogger()
    {
        return $this->_logger;
    }
}
