<?php namespace HashOver;

// Copyright (C) 2015-2021 Jacob Barkdull
// This file is part of HashOver.
//
// HashOver is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// HashOver is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with HashOver. If not, see <http://www.gnu.org/licenses/>.


class HTMLTag
{
protected $tag;
protected $usesPrettyPrint = true;
protected $isSingleton;
protected $attributes = array ();
protected $children = array ();

public function __construct ($tag = '', $attributes = '', $pretty = true, $singleton = false, $spaced = true)
{
if (!is_string ($tag) or !$this->isWord ($tag)) {
$this->throwError ('Tag must have a single word String value.');
return;
}

if (!is_bool ($singleton)) {
$this->throwError ('Singleton parameter must have a Boolean value.');
return;
}

if (!is_bool ($pretty)) {
$this->throwError ('Pretty Print parameter must have a Boolean value.');
return;
}

$this->tag = !empty ($tag) ? $tag : 'span';
$this->usesPrettyPrint = $pretty;
$this->isSingleton = $singleton;

switch (gettype ($attributes)) {
case 'object': {
$this->appendChild ($attributes);
break;
}

case 'array': {
$this->createAttributes ($attributes, $spaced);
break;
}

default: {
$this->innerHTML ($attributes);
break;
}
}
}

public function __get ($name)
{
switch ($name) {
case 'innerHTML': {
return $this->getInnerHTML ();
}
}
}

protected function isWord ($string)
{
if (empty ($string)) {
return false;
}

if (!preg_match ('/[a-z0-9:-_.]+/iS', $string)) {
return false;
}

return true;
}

protected function throwError ($error)
{
$backtrace = debug_backtrace ();
$line = $backtrace[1]['line'];

throw new \Exception (
'Error on line ' . $line . ': ' . $error
);
}

public function getInnerHTML ($indention = '')
{
$inner_html = array ();

foreach ($this->children as $child) {
if (is_object ($child)) {
$inner_html[] = $child->asHTML ($indention);
continue;
}

$inner_html[] = $child;
}

$glue = PHP_EOL . PHP_EOL . $indention;

return implode ($glue, $inner_html);
}

public function createAttribute ($name = '', $value = '', $spaced = true)
{
if (!is_string ($name) or !$this->isWord ($name)) {
$this->throwError ('Attribute name must have a single word String value.');
return false;
}

if (is_array ($value)) {
$glue = ($spaced !== false) ? ' ' : '';
$this->attributes[$name] = implode ($glue, $value);
return true;
}

$this->attributes[$name] = $value;
return true;
}

public function innerHTML ($html = '')
{
if ($this->isSingleton === true) {
$this->throwError ('Singleton tags do not have innerHTML.');
return false;
}

if (!empty ($html)) {
$this->children = array ($html);
}

return true;
}

public function appendInnerHTML ($html = '')
{
if ($this->isSingleton === true) {
$this->throwError ('Singleton tags do not have innerHTML.');
return false;
}

if (!empty ($html)) {
$this->children[] = $html;
}

return true;
}

public function createAttributes (array $attributes, $spaced = true)
{
if (!is_array ($attributes)) {
$this->throwError ('Attributes parameter must have an Array value.');
return;
}

foreach ($attributes as $key => $value) {
switch ($key) {
case 'children': {
if (is_array ($value)) {
for ($i = 0, $il = count ($value); $i < $il; $i++) {
$this->appendChild ($value[$i]);
}
}

break;
}

case 'innerHTML': {
$this->innerHTML ($value);
break;
}

default: {
$this->createAttribute ($key, $value, $spaced);
break;
}
}
}
}

public function appendAttribute ($name = '', $value = '', $spaced = true)
{
if (!is_string ($name) or !$this->isWord ($name)) {
$this->throwError ('Attribute name must have a single word String value.');
return false;
}

if (!empty ($this->attributes[$name])) {
if ($spaced !== false) {
$this->attributes[$name] .= ' ';
}
} else {
$this->attributes[$name] = '';
}

if (is_array ($value)) {
$glue = ($spaced !== false) ? ' ' : '';
$this->attributes[$name] .= implode ($glue, $value);
return true;
}

$this->attributes[$name] .= $value;
return true;
}

public function appendAttributes (array $attributes, $spaced = true)
{
if (!is_array ($attributes)) {
$this->throwError ('Attributes parameter must have an Array value.');
return;
}

foreach ($attributes as $key => $value) {
if ($key === 'innerHTML') {
$this->appendInnerHTML ($value);
continue;
}

$this->appendAttribute ($key, $value, $spaced);
}
}

public function appendChild (HTMLTag $object)
{
if ($this->isSingleton === true) {
$this->throwError ('Singleton tags do not have children.');
return false;
}

if (!is_object ($object)) {
$given_type = ucwords (gettype ($object));
$this->throwError ($given_type . ' given, when Object is expected.');
return false;
}

$this->children[] = $object;
return true;
}

public function asHTML ($indention = '')
{
$attributes = '';

foreach ($this->attributes as $name => $value) {
$value = str_replace ('"', '&quot;', $value);
$attributes .= ' ' . $name . '="' . $value . '"';
}

$tag = '<' . $this->tag . $attributes . '>';

if ($this->isSingleton === false) {
if (!empty ($this->children)) {
$inner_html = $this->getInnerHTML ();

if ($this->usesPrettyPrint !== false) {
$tag .= PHP_EOL . "\t";
$tag .= str_replace (PHP_EOL, PHP_EOL . "\t", $inner_html);
$tag .= PHP_EOL;
} else {
$tag .= $inner_html;
}
}

$tag .= '</' . $this->tag . '>';
}

if (!empty ($indention)) {
return str_replace (PHP_EOL, PHP_EOL . $indention, $tag);
}

return $tag;
}
}