<?php
// +-------------------------------------------------+
// © 2002-2004 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: Converter.php,v 1.6.2.2 2025/06/27 14:20:51 rtigero Exp $

namespace Pmb\ImportExport\Models\Converters;

use onto_common_uri;
use Pmb\Common\Helper\ParserMessage;
use Pmb\ImportExport\Helper\HelperImportExport;
use Pmb\ImportExport\Models\Ontology\Entity\Entity;
use Pmb\ImportExport\Models\Ontology\importExportOntology;
use Pmb\ImportExport\Models\Ontology\OntologyPMB;
use Pmb\ImportExport\Models\Ontology\Property\Property;
use Pmb\ImportExport\Models\Ontology\Store;
use Pmb\ImportExport\Models\Sources\Source;
use stdClass;

abstract class Converter
{
    use ParserMessage;
    private static $subConverters = null;

    public Source $source;
    public array $rules = [];
    public stdClass $settings;
    protected importExportOntology $ontologySource;
    protected OntologyPMB $ontologyPMB;
    protected Store $store;
    protected array $data = [];
    protected array $mappings = [];
    protected string $pmbEntityType = "";
    protected string $sourceEntityType = "";
    protected array $triples = [];
    protected array $deleteTriples = [];
    protected string $outFormat = "";
    protected array $currentValues = [];


    /**
     * Retourne un tableau des sous-convertisseurs en fonction de la classe appelante
     *
     * @return array
     */
    final public static function getSubConverters()
    {
        if (! is_null(static::$subConverters)) {
            return static::$subConverters;
        }
        static::$subConverters = [];

        $converterClass = static::class;
        $converterName = substr($converterClass, strrpos($converterClass, '\\') + 1);
        $converterClass = substr($converterClass, 0, strrpos($converterClass, '\\'));
        $subConvertersDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . "$converterName" . DIRECTORY_SEPARATOR . 'SubConverters' . DIRECTORY_SEPARATOR;
        $subConvertersFiles = glob($subConvertersDir . '*.php');

        foreach ($subConvertersFiles as $file) {
            $className = basename($file, '.php');
            $fullClassName = $converterClass . '\\SubConverters\\' . $className;
            if (class_exists($fullClassName)) {
                static::$subConverters[] = [
                    "label" => static::getMessages()[$className],
                    "value" => $fullClassName,
                    "name" => $className
                ];
            }
        }

        return static::$subConverters;
    }

    /**
     * Exécution de la conversion
     */
    public function execute(): void
    {
        foreach ($this->data as $uri => $entities) {
            $entity = $this->ontologySource->getEntityByURI($uri);
            $this->sourceEntityType = $entity->name;
            $this->getPMBEntityType($entity);
            if ($this->pmbEntityType == "") {
                continue;
            }
            foreach ($entities as $entityURI => $values) {
                $this->setEntityRDFType($entityURI);
                $this->currentValues = $values;
                if (!$this->checkSourceEntity($values)) {
                    continue;
                }
                //On applique les règles d'entités
                foreach ($this->rules["entityRules"] as $subConverterClass) {
                    if (class_exists($subConverterClass)) {
                        $subConverter = new $subConverterClass();
                        $subConverter->fillSubConverter($this);
                        $subConverter->convert($entityURI, $values);
                    }
                }
                foreach ($values as $propURI => $value) {
                    $property = $this->ontologySource->getPropertyByURI($propURI);
                    if (method_exists($this, "convert_" . $property->name) && ! empty($value)) {
                        $this->{"convert_" . $property->name}($entityURI, $value);
                    }
                    if (array_key_exists($property->name, $this->rules["fieldRules"]) && class_exists($this->rules["fieldRules"][$property->name])) {
                        $subConverter = new $this->rules["fieldRules"][$property->name]();
                        $subConverter->fillSubConverter($this);
                        $subConverter->convert($entityURI, $value);
                    }
                }
                $this->applyMappings($entityURI, $values);
                $this->storeTriples();
            }
        }
    }

    public function init()
    {
        $this->ontologySource = $this->source->getOntology();
        $this->ontologyPMB = OntologyPMB::getInstance();
        $this->store = $this->source->getRDFTransformer()->getStore();
        $this->data = $this->store->getGraphData();
    }

    protected function applyMappings($entityURI, $values)
    {
        if (! isset($this->mappings[$this->pmbEntityType])) {
            return;
        }

        $mapping = $this->mappings[$this->pmbEntityType];
        foreach ($mapping as $sourcePropName => $PmbPropName) {
            $pmbProperty = $this->ontologyPMB->getPropertyByName($PmbPropName);
            $sourceProperty = $this->ontologySource->getPropertyByName($sourcePropName);
            if (! isset($pmbProperty) || ! isset($sourceProperty)) {
                continue;
            }
            if ($this->checkPropertiesCompatibility($sourceProperty, $pmbProperty)) {
                foreach ($values[$sourceProperty->uri] as $value) {
                    if ("" != $value) {
                        $this->addTriple($entityURI, $pmbProperty->uri, $this->getFormatedValue($pmbProperty, $value));
                        $this->deleteTriple($entityURI, $sourceProperty->uri, $value, true);
                        if ($pmbProperty->maxCardinality <= 1) {
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * Vérifie la compatibilité des propriétés entre la source et PMB
     */
    protected function checkPropertiesCompatibility($sourceProperty, $pmbProperty)
    {
        $pmbRanges = array_keys($pmbProperty->ranges);
        foreach ($sourceProperty->ranges as $range) {
            if (! in_array($range->uri, $pmbRanges)) {
                return false;
            }
        }
        return true;
    }

    private function insertTriple($subject, $predicate, $object, $delete = false)
    {
        $namespaces = array_values($this->store->ns);
        if (HelperImportExport::isURI($subject, $namespaces)) {
            $subject = "<$subject>";
        }
        if (HelperImportExport::isURI($predicate, $namespaces)) {
            $predicate = "<$predicate>";
        }
        if (HelperImportExport::isURI($object, $namespaces)) {
            $object = "<$object>";
        } else if (! HelperImportExport::isNamespace($object, array_keys($this->store->ns))) {
            $object = '"' . addslashes($object) . '"';
        }
        if ($delete) {
            $this->deleteTriples[] = "$subject $predicate $object";
        } else {
            $this->triples[] = "$subject $predicate $object";
        }
    }

    protected function addTriple($subject, $predicate, $object)
    {
        $this->insertTriple($subject, $predicate, $object);
    }

    protected function deleteTriple($subject, $predicate, $object)
    {
        $this->insertTriple($subject, $predicate, $object, true);
    }

    protected function setEntityRDFType($entityURI)
    {
        $pmbEntity = $this->ontologyPMB->getEntityByName($this->pmbEntityType);
        $sourceEntity = $this->ontologySource->getEntityByName($this->sourceEntityType);
        if ($pmbEntity->uri) {
            $this->deleteTriple($entityURI, "rdf:type", $sourceEntity->uri);
            $this->addTriple($entityURI, "rdf:type", $pmbEntity->uri);
        }
    }

    public function getOutFormat()
    {
        return $this->outFormat;
    }

    protected function storeTriples()
    {
        if (! empty($this->triples)) {
            $this->store->storeTriples($this->triples);
        }

        if (! empty($this->deleteTriples)) {
            $this->store->deleteTriples($this->deleteTriples);
        }

        $this->triples = [];
        $this->deleteTriples = [];
    }

    /**
     * Remplit un subconverter avec les propriétés du converter
     * Attention, les propriétés sont passées par référence
     *
     * @param Converter $caller
     * @return void
     */
    final protected function fillSubConverter(&$caller)
    {
        foreach ($caller as $property => &$value) {
            if (property_exists($this, $property)) {
                $this->$property = &$value;
            }
        }
    }

    public function setRules(array $rules)
    {
        $this->rules = array(
            "entityRules" => array(),
            "fieldRules" => array()
        );

        foreach ($rules as $rule) {
            if ($rule->range == "entity") {
                $this->rules["entityRules"][$rule->field] = $rule->subConverter;
            } else {
                $this->rules["fieldRules"][$rule->field] = $rule->subConverter;
            }
        }
    }

    /**
     * Retourne un URI temporaire permettant d'ajouter une nouvelle entité dans le store
     *
     * @param string $entityType
     * @return string
     */
    protected function getNewURI(string $entityType): string
    {
        return onto_common_uri::get_temp_uri($this->store->getGraphURI() . "#" . $entityType);
    }

    protected function getValue(string $sourceFieldName): array
    {
        $property = $this->ontologySource->getPropertyByName($sourceFieldName);
        if (array_key_exists($property->uri, $this->currentValues)) {
            return $this->currentValues[$property->uri];
        }
        return array();
    }

    /**
     * Retourne la valeur à insérer dans le store lors du mapping en fonction de la propriété
     *
     * @param Property $property
     * @param string $value
     * @return mixed
     */
    protected function getFormatedValue(Property $property, string $value): string
    {
        switch (true) {
            case isset($property->data) && array_key_exists("options", $property->data):
                foreach ($property->data["options"] as $option) {
                    if (strtolower(trim($option["label"])) == strtolower(trim($value))) {
                        return $option["value"];
                    }
                }
            default:
                return $value;
        }
    }

    /**
     * Récupère le type de l'entité PMB correspondant au type de l'entité source
     *
     * @param Entity $sourceEntityURI
     * @return void
     */
    abstract protected function getPMBEntityType(Entity $sourceEntity): void;

    /**
     * Permet de vérifier si l'entité source est bien convertible
     *
     * @param array $values
     * @return bool
     */
    abstract protected function checkSourceEntity(array $values): bool;
}
