<?php
/**
 * @copyright: Copyright © 2017 Firebear Studio. All rights reserved.
 * @author   : Firebear Studio <fbeardev@gmail.com>
 */

namespace Firebear\ImportExport\Model\Import\Source;

use Firebear\ImportExport\Model\Source\Platform\PlatformInterface;
use Firebear\ImportExport\Traits\Import\Map as ImportMap;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Filesystem;

/**
 * CSV import adapter
 */
class Json extends AbstractSource
{
    use ImportMap;

    const CREATE_ATTRIBUTE = 'create_attribute';

    /**
     * @var Resource
     */
    protected $stream;

    /**
     * @var \JsonStreamingParser\Listener\InMemoryListener $listener
     */
    protected $listener;

    protected $maps;

    protected $extension = 'json';

    protected $mimeTypes = [];

    /**
     * Platform
     *
     * @var \Firebear\ImportExport\Model\Source\Platform\PlatformInterface
     */
    protected $platform;

    protected $entities;

    /**
     * Iterator Lock Flag
     *
     * @var bool
     */
    protected $_lock = false;

    /**
     * Prepared Items
     *
     * @var array
     */
    protected $_items = [];

    /**
     * Initialize Adapter
     *
     * @param string $file
     * @param Filesystem $filesystem
     * @param PlatformInterface|null $platform
     * @param array $data
     * @throws \Exception
     */
    public function __construct(
        $file,
        Filesystem $filesystem,
        ?PlatformInterface $platform = null,
        $data = []
    ) {
        $directory = $this->getDirectory($file, $filesystem);
        $result = $this->checkMimeType($directory->getAbsolutePath($file));
        if ($result !== true) {
            throw new \RuntimeException($result);
        }

        $this->platform = $platform;
        $this->listener = new \JsonStreamingParser\Listener\InMemoryListener();
        try {
            $this->stream = fopen($directory->getAbsolutePath($file), 'r');
            $parser = new \JsonStreamingParser\Parser($this->stream, $this->listener);
            $parser->parse();
            fclose($this->stream);

            $importData = $this->listener->getJson();
            if (!empty($data['predefined_structure']) && !empty($data['path_to_entity_structure'])) {
                $importData = $this->usePathToEntityStructure($data['path_to_entity_structure'], $importData);
            }
            $parseData = $platform && method_exists($platform, 'prepareData')
                ? $platform->prepareData($importData)
                : $importData;
            $finalData = false;
            foreach ($parseData as $datum) {
                if (\is_array($datum)) {
                    $finalData = $datum;
                    break;
                }
            }
            $this->entities = $finalData ?? [];
        } catch (\Exception $e) {
            fclose($this->stream);
            throw $e;
        }

        $this->_colNames = array_keys($this->entities[0] ?? $this->entities ?? []);
        parent::__construct(
            $this->_colNames
        );
    }

    /**
     * Use the path to the parent element key to define the entity structure in the import file
     *
     * @param $path
     * @param $importData
     * @return mixed
     */
    protected function usePathToEntityStructure($path, $importData)
    {
        $parentNodePath = explode('/', $path);
        foreach ($parentNodePath as $path) {
            if (!empty($importData[$path])) {
                $importData = $importData[$path];
            }
        }
        return $importData;
    }

    /**
     * Close file handle
     *
     * @return void
     */
    public function destruct()
    {
        fclose($this->stream);
    }

    /**
     * Read next line from JSON-file
     *
     * @return array|bool
     */
    protected function _getNextRow()
    {
        $parsed = [];
        /* reader prepared items */
        if ($this->_lock) {
            foreach ($this->_items as $field => $items) {
                foreach ($items as $key => $item) {
                    $parsed[$field] = $item;
                    unset($this->_items[$field][$key]);
                    break;
                }
                if (0 == count($this->_items[$field])) {
                    unset($this->_items[$field]);
                }
                break;
            }
            if (0 == count($this->_items)) {
                $this->_items = [];
                $this->_lock = false;
            }
            return $parsed;
        } elseif (count($this->entities)) {
            $fields = array_shift($this->entities);
            foreach ($fields as $name => $data) {
                if ($name == self::CREATE_ATTRIBUTE) {
                    $text = 'attribute';
                    $valueText = '';
                    foreach ($data as $nameAttribute => $valueAttribute) {
                        if ($nameAttribute != 'value') {
                            $text .= "|" . $nameAttribute . ":" . $valueAttribute;
                        } else {
                            $valueText = $valueAttribute;
                        }
                    }
                    $parsed[$text] = $valueText;
                    continue;
                } elseif (is_array($data)) { /* prepare child children */
                    $this->_items[$name] = [];
                    foreach ($data as $childItem) {
                        if (!is_array($childItem)) {
                            continue;
                        }
                        $item = [];
                        foreach ($childItem as $field => $fieldValue) {
                            if (is_array($fieldValue)) {
                                $subKey = $name . '_' . $field;
                                $this->_items[$subKey] = [];
                                foreach ($fieldValue as $subItemField) {
                                    $subItem = [];
                                    foreach ($subItemField as $subField => $subFieldValue) {
                                        $subItem[$subField] = $subFieldValue;
                                    }
                                    $this->_items[$subKey][] = $subItem;
                                }
                                $item[$field] = '';
                            } else {
                                $item[$field] = (string)$fieldValue;
                            }
                        }
                        $this->_items[$name][] = $item;
                    }
                    $value = '';
                } else {
                    $value = (string)$data;
                    if (is_string($value) && strpos($value, "'") !== false) {
                        $this->_foundWrongQuoteFlag = true;
                    }
                }
                $parsed[$name] = $value;
            }
            $this->_lock = (0 < count($this->_items)) ? true : false;
            return $parsed;
        } else {
            return false;
        }
    }

    /**
     * Rewind the \Iterator to the first element (\Iterator interface)
     *
     * @return void
     */
    public function rewind()
    {
        $this->_items = [];
        $this->_lock = false;
        parent::rewind();
    }

    /**
     * Return the current element
     *
     * @return array
     */
    public function current()
    {
        return $this->replaceValue(
            $this->changeFields($this->_row)
        );
    }

    /**
     * @return mixed
     */
    public function getColNames()
    {
        $childEntitiesColumns = $this->getColumnsFromChildEntities();
        $this->_colNames = array_merge($this->_colNames, $childEntitiesColumns);
        return $this->replaceColumns($this->_colNames);
    }

    /**
     * Add ColumnsFromChildEntities
     */
    public function getColumnsFromChildEntities()
    {
        $entity = $this->entities[0];
        $itemKeys = [];
        foreach ($this->childEntities as $childEntity) {
            if (!empty($entity[$childEntity])) {
                $item = $entity[$childEntity][0];
                foreach ($item as $param => $value) {
                    $itemKeys[$childEntity . ':' . $param] = true;
                }
            }
        }
        return array_keys($itemKeys);
    }

    /**
     * @param $platform
     * @return $this
     */
    public function setPlatform($platform)
    {
        $this->platform = $platform;

        return $this;
    }

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