<?php
/**
 * This class is used for generate code (SQL) from XML definition files
 * presents on instance sections.
 *
 * @author Camilo Carromeu <camilo@carromeu.com>
 * @category class
 * @package core
 * @subpackage database
 * @copyright 2005-2017 Titan Framework
 * @license http://www.titanframework.com/license/ BSD License (3 Clause)
 */
class DatabaseMaker
{
	private $file = '';

	private $primary = '';

	private $table = '';

	private $fields = array ();

	public function __construct ()
	{
		$args = func_get_args();

		$fileName = FALSE;

		foreach ($args as $trash => $arg)
		{
			if (!file_exists ($arg) || is_dir ($arg))
				continue;

			$fileName = $arg;

			break;
		}

		if ($fileName === FALSE)
			throw new Exception ('Arquivo XML não encontrado.');

		$xml = new Xml ($fileName);

		$array = $xml->getArray ();

		if (!isset ($array ['form'][0]))
			throw new Exception ('A tag &lt;form&gt;&lt;/form&gt; não foi encontrada no XML ['. $fileName .']!');

		$array = $array ['form'][0];

		$this->file = $fileName;

		if (array_key_exists ('table', $array))
			$this->table = $array ['table'];

		if (array_key_exists ('primary', $array))
			$this->primary = $array ['primary'];

		if (array_key_exists ('field', $array) && is_array ($array ['field']))
			foreach ($array ['field'] as $trash => $field)
				if ($obj = Type::factory ($this->getTable (), $field))
					$this->fields [$obj->getAssign ()] = $obj;

		if (array_key_exists ('group', $array) && is_array ($array ['group']))
			foreach ($array ['group'] as $trash => $group)
				if (array_key_exists ('field', $group) && is_array ($group ['field']))
					foreach ($group ['field'] as $trash => $field)
						if ($obj = Type::factory ($this->getTable (), $field))
							$this->fields [$obj->getAssign ()] = $obj;

		reset ($this->fields);
	}

	public function getFile ()
	{
		return $this->file;
	}

	public function getTable ()
	{
		return $this->table;
	}

	public function getPrimary ()
	{
		return $this->primary;
	}

	public function getFields ()
	{
		return $this->fields;
	}

	public function getSize ()
	{
		return sizeof ($this->fields);
	}

	public function getUniques ()
	{
		$uniques = array ();

		foreach ($this->fields as $key => $field)
			if ($field->isUnique ())
				$uniques [$key] = $field;

		return $uniques;
	}

	public function getRequireds ()
	{
		$requireds = array ();

		foreach ($this->fields as $key => $field)
			if ($field->isRequired ())
				$requireds [$key] = $field;

		return $requireds;
	}

	public function getField ($group = FALSE)
	{
		$field = each ($this->fields);

		while ($field !== FALSE)
		{
			if ($group === FALSE || (array_key_exists ($group, $this->groups) && in_array ($field ['value']->getAssign (), $this->groups [$group])))
				return $field ['value'];

			$field = each ($this->fields);
		}

		reset ($this->fields);

		return NULL;
	}

	public function makeTable ()
	{
		$fields = array ();
		while ($field = $this->getField ())
			if (trim (self::toSql ($field)) != '')
				$fields [] = self::toSql ($field);

		$constraints = array ();
		while ($field = $this->getField ())
			if (trim (self::toConstraint ($field)) != '')
				$constraints [] = self::toConstraint ($field);

		$strFields = implode (", ", $fields);

		if (strpos ($strFields, '_user') === FALSE)
		{
			$fields [] = "_user INTEGER NOT NULL";
			$constraints [] = "CONSTRAINT ". str_replace (".", "_", $this->getTable ()) ."_user_fk FOREIGN KEY (_user) REFERENCES ". Database::singleton ()->getSchema () ."._user(_id) ON DELETE RESTRICT ON UPDATE CASCADE NOT DEFERRABLE";
		}

		if (strpos ($strFields, '_create') === FALSE)
			$fields [] = "_create TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL";

		if (strpos ($strFields, '_update') === FALSE)
			$fields [] = "_update TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL";

		return "CREATE TABLE ". $this->getTable () ." (\n  id SERIAL PRIMARY KEY,\n  ". implode (",\n  ", $fields) . (sizeof ($constraints) ? ",\n  ". implode (",\n  ", $constraints) : "") ."\n) WITHOUT OIDS; \n\n";
	}

	public function getDependencies ()
	{
		$deps = array ();
		while ($field = $this->getField ())
			if (method_exists ($field, 'getLink'))
				$deps [] = $field->getLink ();

		return $deps;
	}

	public function getGroup ()
	{
		$group = each ($this->groupsInfo);

		if ($group !== FALSE)
			return new Group ($group ['value']);

		reset ($this->groupsInfo);

		return NULL;
	}

	public static function toSql ($field)
	{
		if (!is_object ($field))
			return $field ." VARCHAR(256) DEFAULT NULL";

		$instance = Instance::singleton ();

		$type = get_class ($field);

		do
		{
			$file = $instance->getTypePath ($type) .'toDbMaker.php';

			if (file_exists ($file))
				return include $file;

			$type = get_parent_class ($type);

		} while ($type != 'Type' && $type !== FALSE);

		return $field->getColumn () ." VARCHAR(256) DEFAULT NULL";
	}

	public static function toConstraint ($field)
	{
		if (!is_object ($field))
			return "";

		$instance = Instance::singleton ();

		$type = get_class ($field);

		do
		{
			$file = $instance->getTypePath ($type) .'toConstraint.php';

			if (file_exists ($file))
				return include $file;

			$type = get_parent_class ($type);

		} while ($type != 'Type' && $type !== FALSE);

		return "";
	}
}