<?php

namespace Entity;

abstract class ModelBase
{
	private $fieldsValues = array();
	protected static $dbo = null;
    private $changed = array();
    private static $config = null;
    private $id;

    protected abstract function install();

	public function __construct($id = null)
	{
        self::init();
        $this->id = null;
        if ($id !== null && is_numeric($id))
            $this->selectById($id);
	}

	public static function init($config = null)
	{
		if (self::$dbo !== null)
            return true;
		// @codeCoverageIgnoreStart
		// Load config from written file; this file is created from setup script
		if ($config === null && self::$config === null)
		{
			self::$config = @include("core/config.inc.php");
			if (empty(self::$config))
				return false;
		}
		// @codeCoverageIgnoreEnd
		else
			self::$config = $config;
        self::$dbo = new \PDO(self::$config[0], self::$config[1], self::$config[2]);
        return true;
	}

    public static function getDbPrefix()
    {
        return self::$config[3];
    }

    public function getTableName()
    {
        $className = new \ReflectionClass($this);
        return $this->getDbPrefix().strtolower($className->getShortName());
    }

    public static function setup()
    {
        $tables = array("Admin", "User", "Address", "Cart", "Category", "Product", "CartProduct", "Meta", "Cms", "Config", "Module", "ModuleHook");

        self::init();
        self::$dbo->beginTransaction();
        try
        {
            foreach ($tables as $i)
            {
                $i = "Entity\\".$i;
                $table = new $i();
                if ($table->install() != true)
                    throw new \Exception("{$i}: Cannot table setup failure");
            }
        }
        catch (\Exception $e)
        {
            self::$dbo->rollBack();
			error_log($e->getMessage());
            return false;
        }
        self::$dbo->commit();
        return true;
    }

    public function getMeta($lang=null)
    {
        $fetcher = new \Entity\Meta();
        return $fetcher->query(array("type" => get_class($this)));
    }

    public function __get($key)
    {
        if ($key == "meta")
            return $this->getMeta();
        if ($key == "id")
            return $this->id === null ? null : (int) $this->id;
        if (!isset($this->fieldsValues[$key]))
            return null;
        return $this->fieldsValues[$key];
    }

    public function __set($key, $value)
    {
        if ($value instanceof \DateTime)
            $value = $value->format("Y-m-d");
        $this->fieldsValues[$key] = $value;
        if (is_bool($value))
            $this->changed[$key] = $value ? 1 : 0;
		else if ($value === null)
			$this->changed[$key] = "NULL";
        else
            $this->changed[$key] = self::$dbo->quote($value);
        return $value;
    }

    public function save()
    {
        \Tools\Hooks::trigger("onBeforeEntitySave");
        \Tools\Hooks::trigger("onBeforeEntitySave".$this->getTableName());
        if ($this->id === null)
        {
            if (empty ($this->changed))
            {
                $query = "INSERT INTO `{$this->getTableName()}` () VALUES ()";
                $result = self::$dbo->exec($query);
                if (!$result)
                    throw new \Exception(self::$dbo->errorInfo()[2]);
            }
            else
            {
                $query = "INSERT INTO `{$this->getTableName()}` (`" .implode("`,`", array_keys($this->changed)) . "`) VALUES (" . implode(",", $this->changed) . ")";
                $result = self::$dbo->exec($query);
                if (!$result)
                    throw new \Exception(self::$dbo->errorInfo()[2]);
                $this->changed = array();
            }
            $this->id = self::$dbo->lastInsertId();
        }
        else
        {
            if (!empty($this->changed))
            {
                if ($this->id === false)
                    throw new \Exception("Cannot update private row");
                $query = "UPDATE {$this->getTableName()} SET ";
                $newValues = array();
                foreach ($this->changed as $i => $j)
                    $newValues[] = "`{$i}`=" . $j;
                $query .= implode(",",$newValues)." WHERE id={$this->id}";
                $result = self::$dbo->exec($query);
                if (!$result)
                    throw new \Exception(self::$dbo->errorInfo()[2]);
                $this->changed = array();
            }
        }
        \Tools\Hooks::trigger("onAfterEntitySave");
        \Tools\Hooks::trigger("onAfterEntitySave".$this->getTableName());
    }

	/**
	 * Remove entity from database
	 * $entity->delete() then $entity->save() should save entity with a new id and the same data
	 * @return Boolean true on success
	**/
	public function delete()
	{
		if ($this->id === false)
			return true;
        \Tools\Hooks::trigger("onBeforeEntityDelete");
        \Tools\Hooks::trigger("onBeforeEntityDelete".$this->getTableName());
		$id = (int) $this->id;
		foreach ($this->fieldsValues as $i => $j)
			if (!isset($this->changed[$i]))
				$this->changed[$i] = $j;
		if (self::$dbo->exec("DELETE FROM {$this->getTableName()} WHERE `id`={$id}") === false)
			return false;
        \Tools\Hooks::trigger("onAfterEntityDelete");
        \Tools\Hooks::trigger("onAfterEntityDelete".$this->getTableName());
		return true;
	}

    public function selects($criteria = null, $orderBy = null)
    {
        $query = "SELECT * FROM {$this->getTableName()}";

        if (!empty($criteria))
        {
            $subQuery = array();
            foreach ($criteria as $i => $j)
            {
                if ($j == null)
                    $subQuery[] = "`{$i}` IS NULL";
                else if (is_array($j))
                {
                    $inArray = [];
                    foreach ($j as $k)
                        $inArray[] = self::$dbo->quote($k);
                    $subQuery[] = "`{$i}` IN (".implode(",", $inArray).")";
                }
                else
                    $subQuery[] = "`{$i}`=".self::$dbo->quote($j);
            }
            $query .= " WHERE ".implode(" AND ", $subQuery);
        }
        if (!empty($orderBy))
        {
            $_orderBy = array();
            foreach ($orderBy as $i => $j)
            {
                if (is_numeric($i))
                    $_orderBy[] = "`{$j}` ASC";
                else
                {
                    $orderType = "ASC";
                    if (strtoupper($j == "DESC"))
                        $orderType = "DESC";
                    $_orderBy[] = "`{$i}` {$orderType}";
                }
            }
            $query .= " ORDER BY ".implode(",", $_orderBy);
        }
        $result = self::$dbo->query($query, \PDO::FETCH_ASSOC);
        if ($result === false)
            throw new \Exception(self::$dbo->errorInfo()[2]);
        $resultObj = array();
        $className = get_class($this);
        foreach ($result as $i)
        {
            $iObj = new $className();
            $iObj->populate($i);
            $resultObj[] = $iObj;
        }
        return $resultObj;
    }

    public function selectById($id)
    {
        $query = "SELECT * FROM {$this->getTableName()} WHERE id=".(int)$id." LIMIT 1";
        $result = self::$dbo->query($query, \PDO::FETCH_ASSOC);
        if ($result === false || empty($result))
            throw new \Exception("Cannot fetch data: ".self::$dbo->errorInfo()[2]);
		$this->populate($result->fetch());
    }

    private function populate($data)
    {
        $this->id = FALSE;
		if ($data === false)
			throw new \Exception("Cannot load entity: no result found");
        foreach ($data as $i => $j)
            $this->fieldsValues[$i] = $j;
        if (isset($this->fieldsValues["id"]))
            $this->id = (int) $this->fieldsValues["id"];
		$this->changed = array();
    }
}

