<?php
/**
 * Created by PhpStorm.
 * User: steven
 * Date: 7/25/19
 * Time: 10:04 AM
 */

namespace Hilco\XMLBuilder;
/**
 * Class XMLUtility
 * General purpose handler for creating XMLs.
 * Currently set to work with SimpleXML library predominantly, but ideally will work with DOM for expandability.
 */
class XMLUtility
{
    protected $xml;
    protected $simpleXML = null;

    public function __construct($attributes)
    {
        if(\Illuminate\Support\Arr::has($attributes, 'xml')){
            $this->xml = \Illuminate\Support\Arr::get($attributes, 'xml');
        }else if(\Illuminate\Support\Arr::has($attributes, 'xmlArray')){
            $this->xml = $this->buildXMLFromArray(\Illuminate\Support\Arr::get($attributes, 'xmlArray'));
        }else if(\Illuminate\Support\Arr::has($attributes, 'simpleXML')){
            $this->simpleXML = \Illuminate\Support\Arr::get($attributes, 'simpleXML', true);
        }

    }

    public function buildXMLFromArray(array $xmlContents, $xmlHeader = null){
        if(!$xmlContents){
            return null;
        }
        $xmlHeader = $xmlHeader ? $xmlHeader : '<?xml version="1.0"?><data></data>';
        $xmlData =  new SimpleXMLElement($xmlHeader);
        $this->array_to_xml($xmlContents, $xmlData);
    }
    /*
     *
     */
    public function array_to_xml(array $data, SimpleXMLElement &$xmlData){
        foreach($data as $key => $value){
            if(is_numeric($key)){
                $key = 'item' . $key;
            }
            if( is_array($value) ) {
                $subnode = $xml_data->addChild($key);
                array_to_xml($value, $subnode);
            } else {
                $xml_data->addChild("$key",htmlspecialchars("$value"));
            }
        }
    }
    public function xml_to_array($xmlString){
        $simpleXML = simplexml_load_string($xmlString, null, LIBXML_NOCDATA);
        $json = json_encode($simpleXML);
        $array = json_decode($json, true);
        return $array;

    }

    public static function retrievePath(\SimpleXMLElement $simpleXMLElement, array $steps){
        $traversal = $simpleXMLElement;
        foreach($steps as $step){
            $traversal = XMLUtility::evaluateStep($traversal, $step);
            if(is_null($traversal)){
                return $traversal;
            }
        }
        return $traversal;
    }


    public static function retrieveStackValues(\SimpleXMLElement $startingPoint, array $pathToStackValues, array $valuePathArray)
    {
        if(!empty($pathToStackValues)){
            $startingPoint = XMLUtility::retrievePath($startingPoint, $pathToStackValues);
        }
        $memberReturnArray = [];
        foreach($startingPoint as $stackMember){
            $valueNameArray = [];
            foreach($valuePathArray as $valueName => $path){
                $valueNameArray[$valueName] =  (string) XMLUtility::retrievePath($stackMember, $path);
            }
            $memberReturnArray[] = $valueNameArray;
        }
        return $memberReturnArray;
    }



    /**
     * Parses the delimited step.
     *  This is mainly for evaluating the case where we need to seek an attribute of a certain element.
     *  May need to change the attribute assessment to instead iterate through a stack of elements and see if any of them
     *  have that same attribute.
     * @param \SimpleXMLElement $traversal
     *  The current tier of XML that we're in.
     * @param $step
     *  The allocated name of the step.
     * @return null|\SimpleXMLElement
     *  Returns the XML tag we move to, or null if it doesn't exists.
     */
    public static function evaluateStep(\SimpleXMLElement $traversal, $step){
        if (strpos($step, '@')) {
            if (strpos($step, '=')){ // Requesting the element that has a certain attribute.
                $traversal = XMLUtility::seekElementOfAttribute($traversal, $step);
            } else {  // Requesting the value of the attribute associated with a particular element.
                $traversal = XMLUtility::seekAttributeOfTargetedElement($traversal, $step);
            }
        } else {
            $traversal = $traversal->{$step};
        }
        return $traversal;
    }

    /**
     * Iterates through a stack of elements and sees if any of them have the desired attribute.
     * @param \SimpleXMLElement $element
     *  Current element we're within. Contains the tag of the element-stack we need to iterate into.
     *  Should probably name this something less horribly ambiguous. Like traversal.
     * @param $attributeToExplode
     *  a@b, where a is the attribute and b is the value. Probably shouldn't rely on receiving a value that needs
     *      extra work to parse.
     * @return null|\SimpleXMLElement
     */
    public static  function seekElementOfAttribute(\SimpleXMLElement $element, $attributeToExplode){
        $elementAttributeRelation = explode('@', $attributeToExplode);
        $elementName = $elementAttributeRelation[0];
        $elements = $element->{$elementName};
        $attributeValueRelation = explode('=', $elementAttributeRelation[1]);
        foreach($elements as $element){
            if($element[$attributeValueRelation[0]] == $attributeValueRelation[1]){
                return $element;
            }
        }
        return null;
    }


    /**
     * Simply retrieves a given attribute within a given element.
     * @param \SimpleXMLElement $element
     *  XML Element that has the attribute we want.
     * @param $attributeToExplode
     *  Attribute string.
     * @return bool|null|\SimpleXMLElement
     */
    public static function seekAttributeOfTargetedElement(\SimpleXMLElement $element, $attributeToExplode){
        if(is_string($attributeToExplode)){
            $elementAttributeRelation = explode('@', $attributeToExplode);
            $elementName = $elementAttributeRelation[0];
            $attribute = $elementAttributeRelation[1];
        }else if(is_array($attributeToExplode)){
            $attribute = \Illuminate\Support\Arr::get($attributeToExplode, 'attribute', false);
            $elementName = \Illuminate\Support\Arr::get($attributeToExplode, 'elementName', false);
        }else{
            return false;
        }

        if($elementName == false){
            $desiredValue = $element[$attribute];
            return $desiredValue;
        }
        if(isset($element->{$elementName})){
            $desiredValue = $element->{$elementName}[$attribute];
        }else{
            $desiredValue = null;
        }
        return $desiredValue;
    }



}