<?php

namespace Braintree\Xml;

use DateTime;
use DateTimeZone;
use DOMDocument;
use DOMElement;
use DOMText;
use Braintree\Util;

/**
 * Braintree XML Parser
 */
class Parser
{
    /**
     * Converts an XML string into a multidimensional array
     *
     * @param string $xml string to be parsed
     *
     * @return array
     */
    public static function arrayFromXml($xml)
    {
        $document = new DOMDocument('1.0', 'UTF-8');
        $document->loadXML($xml);

        $root = $document->documentElement->nodeName;

        return Util::delimiterToCamelCaseArray([
            $root => self::_nodeToValue($document->childNodes->item(0)),
        ]);
    }

    /**
     * Converts a node to an array of values or nodes
     *
     * @param DOMNode @node
     *
     * @return mixed
     */
    private static function _nodeToArray($node)
    {
        $type = null;
        if ($node instanceof DOMElement) {
            $type = $node->getAttribute('type');
        }

        switch ($type) {
            case 'array':
                $array = [];
                foreach ($node->childNodes as $child) {
                    $value = self::_nodeToValue($child);
                    if ($value !== null) {
                        $array[] = $value;
                    }
                }
                return $array;
            case 'collection':
                $collection = [];
                foreach ($node->childNodes as $child) {
                    $value = self::_nodetoValue($child);
                    if ($value !== null) {
                        if (!isset($collection[$child->nodeName])) {
                            $collection[$child->nodeName] = [];
                        }
                        $collection[$child->nodeName][] = self::_nodeToValue($child);
                    }
                }
                return $collection;
            default:
                $values = [];
                if ($node->childNodes->length === 1 && $node->childNodes->item(0) instanceof DOMText) {
                    return $node->childNodes->item(0)->nodeValue;
                } else {
                    foreach ($node->childNodes as $child) {
                        if (!$child instanceof DOMText) {
                            $values[$child->nodeName] = self::_nodeToValue($child);
                        }
                    }
                    return $values;
                }
        }
    }

    /**
     * Converts a node to a PHP value
     *
     * @param DOMNode $node
     *
     * @return mixed
     */
    private static function _nodeToValue($node)
    {
        $type = null;
        if ($node instanceof DOMElement) {
            $type = $node->getAttribute('type');
        }

        switch ($type) {
            case 'datetime':
                return self::_timestampToUTC((string) $node->nodeValue);
            case 'date':
                return new DateTime((string) $node->nodeValue);
            case 'integer':
                return (int) $node->nodeValue;
            case 'boolean':
                $value =  (string) $node->nodeValue;
                if (is_numeric($value)) {
                    return (bool) $value;
                } else {
                    return ($value !== "true") ? false : true;
                }
            case 'array':
            case 'collection':
                return self::_nodeToArray($node);
            default:
                if ($node->hasChildNodes()) {
                    return self::_nodeToArray($node);
                } elseif (trim($node->nodeValue) === '') {
                    return null;
                } else {
                    return $node->nodeValue;
                }
        }
    }


    /**
     * Converts XML timestamps into DateTime instances
     *
     * @param string $timestamp
     *
     * @return DateTime
     */
    private static function _timestampToUTC($timestamp)
    {
        $tz = new DateTimeZone('UTC');
        $dateTime = new DateTime($timestamp, $tz);
        $dateTime->setTimezone($tz);
        return $dateTime;
    }
}
