<?php
/**
 * YANA library
 *
 * Software:  Yana PHP-Framework
 * Version:   {VERSION} - {DATE}
 * License:   GNU GPL  http://www.gnu.org/licenses/
 *
 * This program: can be redistributed and/or modified under the
 * terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 *
 * This notice MAY NOT be removed.
 *
 * @package  yana
 * @license  http://www.gnu.org/licenses/gpl.txt
 */

/**
 * database structure
 *
 * This wrapper class represents the structure of a database
 *
 * @access      public
 * @package     yana
 * @subpackage  database
 */
class XDDL extends File
{
    /**
     * Macros
     *
     * A list of automatically replaced token. The program will replace the one
     * text with the other on input and vice versa on output.
     *
     * @access  private
     * @var     array
     */
    private static $macros = array(
        array(
            'profile_id',
            '<string name="profile_id" notnull="yes" length="128"/>',
            '/<string [^>\/]*?name="profile_id"(?:[^>\/]*?\/>|.*?<\/string>)/s'
        ),
        array(
            'user_created',
            '<string name="user_created" notnull="yes" length="128"/>',
            '/<string [^>\/]*?name="user_created"(?:[^>\/]*?\/>|.*?<\/string>)/s'
        ),
        array(
            'user_modified',
            '<string name="user_modified" length="128"/>',
            '/<string [^>\/]*?name="user_modified"(?:[^>\/]*?\/>|.*?<\/string>)/s'
        ),
        array(
            'time_created',
            '<timestamp name="time_created" notnull="yes"><default>CURRENT_TIMESTAMP</default></timestamp>',
            '/<timestamp [^>\/]*?name="time_created"(?:[^>\/]*?\/>|.*?<\/timestamp>)/s'
        ),
        array(
            'time_modified',
            '<timestamp name="time_modified"><default>CURRENT_TIMESTAMP</default></timestamp>',
            '/<timestamp [^>\/]*?name="time_modified"(?:[^>\/]*?\/>|.*?<\/timestamp>)/s'
        )
    );

    /**
     * Data Description Language object
     *
     * @var  DDLDatabase  database defintion
     * @ignore
     */
    protected $ddl = null;

    /**
     * Create a SML string from a scalar variable, an object, or an array of data.
     *
     * If the file is empty or does not exist, the returned string will be empty.
     *
     * If the database definition has been modified, the changed are applied (but not yet saved) to
     * the file content.
     *
     * Note: Reserved column names are reverted to macros. Changes to these columns will be lost.
     *
     * @access  public
     * @return  string
     */
    public function toXML()
    {
        if (isset($this->ddl) && $this->ddl->isModified()) {
            assert('!isset($xddl); // Cannot redeclare var $xddl');
            $xddl = $this->ddl->serializeToXDDL();
            assert('$xddl instanceof SimpleXMLElement; // Expecting function serializeToXDDL() to return a SimpleXMLElement.');
            assert('!isset($xml); // Cannot redeclare var $xml');
            $xml = $xddl->asXML();
            assert('is_string($xml); // Expecting function toXML() to return a string.');
            unset($xddl);
            // reverse macros
            assert('!isset($macro); // Cannot redeclare var $macro');
            assert('!isset($token); // Cannot redeclare var $token');
            foreach (self::$macros as $macro)
            {
                $token = YANA_LEFT_DELIMITER . '$' . $macro[0] . YANA_RIGHT_DELIMITER;
                $xml = preg_replace($macro[2], $token, $xml);
            }
            unset($macro, $token);
            // store content
            $this->content = explode("\n", $xml);
            assert('is_array($this->content); // Property "content" is expected to be an array.');
            // reset database definition to "unmodified"
            $this->ddl->setModified(false);
            // return result
            return $xml;

        } else {
            assert('is_array($this->content); // Property "content" is expected to be an array.');
            return implode("\n", $this->content);
        }
    }

    /**
     * get XML content as simple XML Element
     *
     * Returns an instance of SimpleXMLElement.
     * If the file is empty or does not exist, the returned element will be empty as well.
     *
     * @access  public
     * @return  SimpleXMLElement
     */
    public function toSimpleXML()
    {
        $xml = $this->toXML();
        assert('is_string($xml);');
        return simplexml_load_string($xml);
    }

    /**
     * get database definition
     *
     * @access  public
     * @return  DDLDatabase
     * @throws  NotFoundException  when file does not exist
     */
    public function toDatabase()
    {
        if (!isset($this->ddl)) {
            $this->ddl = self::_getDatabaseFromPath($this->getPath());
        }
        return $this->ddl;
    }

    /**
     * get database definition by database name
     *
     * Resolves the database name to a file path and retrieves the file.
     *
     * @access  public
     * @static
     * @param   string  $databaseName  database name
     * @return  DDLDatabase
     * @throws  NotFoundException  when file does not exist
     */
    public static function getDatabase($databaseName)
    {
        assert('is_string($databaseName); // Wrong type for argument 1. String expected');
        $path = DDL::getPath($databaseName);
        return self::_getDatabaseFromPath($path);
    }

    /**
     * get database definition from file path
     *
     * @param   string  $path  file path
     * @return  DDLDatabase
     * @throws  NotFoundException  when file does not exist
     */
    private static function _getDatabaseFromPath($path)
    {
        if (!is_file($path)) {
            throw new NotFoundException("No such database definition '$path'.");
        }
        $fileContent = file_get_contents($path);

        $ddl = null;

        // execute macros
        assert('!isset($macro); // Cannot redeclare var $macro');
        assert('!isset($token); // Cannot redeclare var $token');
        foreach (self::$macros as $macro)
        {
            $token = YANA_LEFT_DELIMITER . '$' . $macro[0] . YANA_RIGHT_DELIMITER;
            $fileContent = str_replace($token, $macro[1], $fileContent);
        }
        unset($macro, $token);

        assert('!isset($simpleXml); // Cannot redeclare var $simpleXml');
        try {

            $simpleXml = simplexml_load_string($fileContent);
            unset($fileContent);

            $ddl = DDLDatabase::unserializeFromXDDL($simpleXml, null, $path);

        } catch (Exception $e) {
            Log::report("Error in XDDL-file: '$path'.", E_USER_WARNING, $e->getMessage());
            throw new Warning("Error in XDDL-file.");
        }
        assert('$ddl instanceof DDLDatabase; // Invalid return value. DDLDatabase expected');
        return $ddl;
    }
}

?>