<?php

namespace WpieFw\Settings\Parser;

use WpieFw\Files\WpieFile;
use WpieFw\Helpers\WpieSettingsHelper;
use WpieFw\Exceptions\WpieInvalidArgumentException;

if( ! defined( 'ABSPATH' ) ) exit;

final class WpieXmlSettingsParser extends WpieAbstractSettingsParser
{
	/**
	 * File name for the settings validation schema
	 *
	 * @var string
	 */
	const FILE_NAME_SCHEMA_XSD = 'settings.xsd';

	/**
	 * @var \DOMDocument
	 */
	private $doc;

	/**
	 * Absolute path to settings validation schema
	 *
	 * @var string
	 */
	private $pathSchema;

	/**
	 * Constructor
	 *
	 * @param WpieFile $file
	 */
	public function __construct( WpieFile $file )
	{
		$this->pathSchema = dirname( __FILE__ ) . DIRECTORY_SEPARATOR . self::FILE_NAME_SCHEMA_XSD;

		parent::__construct( $file );
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::load()
	 */
	public function load( $content = '' )
	{
		$settings = [];

		$internalErrors = libxml_use_internal_errors( true );
		libxml_clear_errors();

		$this->doc = new \DOMDocument();
		$this->doc->preserveWhiteSpace = false;

		if( !$this->doc->loadXML( $content ) ) {
			 throw new WpieInvalidArgumentException( sprintf('Could not load settings XML "%s".', $this->file->getFilename() ) );
		}

		if( !$this->doc->schemaValidate( $this->pathSchema ) ) {

			ob_start();
			echo '<pre>';
			print_r(libxml_get_errors());
			echo '</pre>';
			$errors = ob_get_contents();
			ob_end_clean();

			libxml_clear_errors();
			libxml_use_internal_errors( $internalErrors );

			throw new WpieInvalidArgumentException( sprintf('Settings XML file "%s" is not valid:\n%s', $this->file->getFilename(), $errors ) );
		}

		$this->doc->normalizeDocument();
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parseDefaults()
	 */
	public function parseDefaults()
	{
		$fields = $this->doc->getElementsByTagName( 'field' );

		foreach ( $fields as $field ) {
			$name = $field->getAttribute( 'name' );
			$default = $field->getElementsByTagName( 'default' );

			if( '' === $name || 0 === $default->length ) {
				continue;
			}

			$value = $default->item(0)->nodeValue;
			$elem = $field->getElementsByTagName( 'elem' );

			// store checkbox value as boolean instead of string
			if( $elem->length && 'checkbox' === $elem->item(0)->nodeValue ) {
			    $value = ( '1' === $value );
			}

			$this->defaults[$name] = $value;
		}

		return $this->defaults;
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parsePriority()
	 */
	public function parsePriority()
	{
		$this->priority = (int) $this->getSettingsAttribute( 'prio' );

		return $this->priority;
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parseIdx()
	 */
	public function parseIdx()
	{
		$this->idx = $this->getSettingsAttribute( 'idx' );

		return $this->idx;
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parseIsDefault()
	 */
	public function parseIsDefault()
	{
		$this->isDefault = $this->getSettingsAttribute( 'default' );

		return $this->isDefault;
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parseToArray()
	 */
	public function parseToArray()
	{
		$this->array = $this->xmlToArray( $this->doc );
	}

	/**
	 * {@inheritDoc}
	 * @see \WpieFw\Settings\Parser\WpieAbstractSettingsParser::parseI18n()
	 */
	public function parseI18n()
	{
		foreach( $this->array['settings'] as $k => $v ) {
			if( '@attributes' === $k ) {
				continue;
			}

			if( WpieSettingsHelper::isFormGroup( $k ) ) {
				foreach( $v as $v2 ) {
					if( !isset( $v2['field'][0] ) ) {
						$v2['field'] = [ $v2['field'] ];
					}
					if( WpieSettingsHelper::hasFormGroupTitle( $v2 ) ) {
						$this->I18n[] = $v2[self::KEY_GROUP_TITLE];
					}
					if( WpieSettingsHelper::hasFormGroupDescr( $v2 ) ) {
						$this->I18n[] = $v2[self::KEY_GROUP_DESCR];
					}
					if( WpieSettingsHelper::hasFormGroupWarning( $v2 ) ) {
						$this->I18n[] = $v2[self::KEY_GROUP_WARNING];
					}
					$this->parseI18nFields( $v2['field'], $this->I18n );
				}
			} else {
				foreach( $v as $field ) {
					$this->parseI18nFields( $field );
				}
			}
		}
	}

	/**
	 * Parse settings fields for I18n strings
	 *
	 * @param array $fields
	 * @param array $I18n
	 */
	private function parseI18nFields( array $fields = [], &$I18n = [] )
	{
		foreach( $fields as $field ) {
			if( WpieSettingsHelper::hasTitle( $field ) ) {
				$I18n[] = $field[self::KEY_TITLE];
			}
			if( WpieSettingsHelper::hasDescr( $field ) ) {
				$I18n[] = $field[self::KEY_DESCR];
			}
			if( WpieSettingsHelper::hasInnerHtml( $field ) && !WpieSettingsHelper::isTemplate( $field ) ) {
				$I18n[] = $field[self::KEY_INNER];
			}
			if( WpieSettingsHelper::hasSelectOptions( $field ) || WpieSettingsHelper::hasMultiCheckbox( $field ) ) {
				foreach ( WpieSettingsHelper::getMultiOptions( $field ) as $option ) {
					$I18n[] = $option;
				}
			}
		}
	}

	/**
	 * Get a settings attribute value
	 *
	 * @param string $attr
	 *
	 * @return string
	 */
	private function getSettingsAttribute( $attr = '' )
	{
		$value = '';
		$settings = $this->doc->getElementsByTagName( 'settings' );

		foreach ( $settings as $setting ) {
			$value = $setting->getAttribute( $attr );
			if( !empty( $value ) ) {
				break;
			}
		}

		return $value;
	}

	/**
	 * Convert XML DOMNode to array
	 *
	 * @todo replace this method to helpers class
	 *
	 * @copyright https://stackoverflow.com/questions/14553547/what-is-the-best-php-dom-2-array-function
	 *
	 * @param \DOMNode $root
	 *
	 * @return array
	 */
	private function xmlToArray( \DOMNode $root )
	{
		$result = [];

		if ( $root->hasAttributes() ) {
			$attrs = $root->attributes;
			foreach ( $attrs as $attr ) {
				$result['@attributes'][$attr->name] = trim( $attr->value );
			}
		}

		if ( $root->hasChildNodes() ) {
			$children = $root->childNodes;

			if ( 1 === $children->length ) {
				$child = $children->item( 0 );
				if ( in_array( $child->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE] ) ) {
					$value = trim( $child->nodeValue );
					if( '' !== $value ) {
						$result['_value'] = $value;
						return ( 1 === count( $result ) ) ? $result['_value'] : $result;
					} else {
						return '';
					}
				}
			}

			$groups = [];
			foreach ( $children as $child ) {
				if( ( XML_TEXT_NODE === $child->nodeType ) && empty( trim( $child->nodeValue ) ) ) {
					continue;
				}
				if ( !isset( $result[$child->nodeName] ) ) {
					$r = $this->xmlToArray( $child );
					// prevent empty arrays as a nodeValue result
					if( is_array( $r ) && !count( $r ) ) {
						continue;
					}

					// all these nodes can have multiple items (group). There for add them as array
					if( in_array( $child->nodeName, [ 'group', 'field', 'option', 'attr' ] ) ) {
						$result[$child->nodeName] = [ $r ];
					} else {
						$result[$child->nodeName] = $r;
					}

				} else {
					// nodeName already exist in result array,
					// flag this nodeName as group if not already done
					// Note: do not confuse $groups with <group>
					if ( !isset( $groups[$child->nodeName] ) ) {
						$groups[$child->nodeName] = 1;
					}

					$result[$child->nodeName][] = $this->xmlToArray( $child );
				}
			}
		}

		return $result;
	}
}