<?php
/* EXPERIMENTAL
 *
 * Copyright (C) 2016		ATM Consulting			<support@atm-consulting.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it 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 <https://www.gnu.org/licenses/>.
 */

/**
 *	\file       htdocs/core/class/coreobject.class.php
 *	\ingroup    core
 *	\brief      File of class to manage all object. Might be replace or merge into commonobject
 */

require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';

// TODO Remove this class (used in Expensereportik and ExpenseReportRule
/**
 * CoreObject
 */
class CoreObject extends CommonObject
{
	public $withChild = true;

	/**
	 *  @var Array $_fields Fields to synchronize with Database
	 */
	protected $fields = array();

	/**
	 *  Constructor
	 *
	 *  @param      DoliDB		$db      Database handler
	 */
	public function __construct(DoliDB &$db)
	{
		$this->db = $db;
	}

	/**
	 * Function to init fields
	 *
	 * @return bool
	 */
	protected function init()
	{
		$this->id = 0;
		$this->datec = 0;
		$this->tms = 0;

		if (!empty($this->fields)) {
			foreach ($this->fields as $field => $info) {
				if ($this->isDate($info)) {
					$this->{$field} = time();
				} elseif ($this->isArray($info)) {
					$this->{$field} = array();
				} elseif ($this->isInt($info)) {
					$this->{$field} = (int) 0;
				} elseif ($this->isFloat($info)) {
					$this->{$field} = (double) 0;
				} else {
					$this->{$field} = '';
				}
			}

			$this->to_delete = false;
			$this->is_clone = false;

			return true;
		} else {
			return false;
		}
	}

	/**
	 * Test type of field
	 *
	 * @param   string  $field  name of field
	 * @param   string  $type   type of field to test
	 * @return  boolean         value of field or false
	 */
	private function checkFieldType($field, $type)
	{
		if (isset($this->fields[$field]) && method_exists($this, 'is_'.$type)) {
			return $this->{'is_'.$type}($this->fields[$field]);
		} else {
			return false;
		}
	}

	/**
	 *	Get object and children from database
	 *
	 *	@param      int			$id       		Id of object to load
	 * 	@param		bool		$loadChild		used to load children from database
	 *	@return     int         				>0 if OK, <0 if KO, 0 if not found
	 */
	public function fetch($id, $loadChild = true)
	{
		$res = $this->fetchCommon($id);
		if ($res > 0) {
			if ($loadChild) {
				$this->fetchChild();
			}
		}

		return $res;
	}


	/**
	 * Function to instantiate a new child
	 *
	 * @param   string  $tabName        Table name of child
	 * @param   int     $id             If id is given, we try to return his key if exist or load if we try_to_load
	 * @param   string  $key            Attribute name of the object id
	 * @param   bool    $try_to_load    Force the fetch if an id is given
	 * @return                          int
	 */
	public function addChild($tabName, $id = 0, $key = 'id', $try_to_load = false)
	{
		if (!empty($id)) {
			foreach ($this->{$tabName} as $k => &$object) {
				if ($object->{$key} === $id) {
					return $k;
				}
			}
		}

		$k = count($this->{$tabName});

		$className = ucfirst($tabName);
		$this->{$tabName}[$k] = new $className($this->db);
		if ($id > 0 && $key === 'id' && $try_to_load) {
			$this->{$tabName}[$k]->fetch($id);
		}

		return $k;
	}


	/**
	 * Function to set a child as to delete
	 *
	 * @param   string  $tabName        Table name of child
	 * @param   int     $id             Id of child to set as to delete
	 * @param   string  $key            Attribute name of the object id
	 * @return                          bool
	 */
	public function removeChild($tabName, $id, $key = 'id')
	{
		foreach ($this->{$tabName} as &$object) {
			if ($object->{$key} == $id) {
				$object->to_delete = true;
				return true;
			}
		}
		return false;
	}


	/**
	 * Function to fetch children objects
	 *
	 * @return void
	 */
	public function fetchChild()
	{
		if ($this->withChild && !empty($this->childtables) && !empty($this->fk_element)) {
			foreach ($this->childtables as &$childTable) {
				$className = ucfirst($childTable);

				$this->{$className} = array();

				$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$childTable.' WHERE '.$this->fk_element.' = '.$this->id;
				$res = $this->db->query($sql);

				if ($res) {
					while ($obj = $this->db->fetch_object($res)) {
						$o = new $className($this->db);
						$o->fetch($obj->rowid);

						$this->{$className}[] = $o;
					}
				} else {
					$this->errors[] = $this->db->lasterror();
				}
			}
		}
	}

	/**
	 * Function to update children data
	 *
	 * @param   User    $user   user object
	 * @return void
	 */
	public function saveChild(User &$user)
	{
		if ($this->withChild && !empty($this->childtables) && !empty($this->fk_element)) {
			foreach ($this->childtables as &$childTable) {
				$className = ucfirst($childTable);
				if (!empty($this->{$className})) {
					foreach ($this->{$className} as $i => &$object) {
						$object->{$this->fk_element} = $this->id;

						$object->update($user);
						if ($this->unsetChildDeleted && isset($object->to_delete) && $object->to_delete == true) {
							unset($this->{$className}[$i]);
						}
					}
				}
			}
		}
	}


	/**
	 * Function to update object or create or delete if needed
	 *
	 * @param   User    $user   User object
	 * @return  int             < 0 if KO, > 0 if OK
	 */
	public function update(User &$user)
	{
		if (empty($this->id)) {
			return $this->create($user); // To test, with that, no need to test on high level object, the core decide it, update just needed
		} elseif (isset($this->to_delete) && $this->to_delete == true) {
			return $this->delete($user);
		}

		$error = 0;
		$this->db->begin();

		$res = $this->updateCommon($user);
		if ($res) {
			$result = $this->call_trigger(strtoupper($this->element).'_UPDATE', $user);
			if ($result < 0) {
				$error++;
			} else {
				$this->saveChild($user);
			}
		} else {
			$error++;
			$this->error = $this->db->lasterror();
			$this->errors[] = $this->error;
		}

		if (empty($error)) {
			$this->db->commit();
			return $this->id;
		} else {
			$this->db->rollback();
			return -1;
		}
	}

	/**
	 * Function to create object in database
	 *
	 * @param   User    $user   User object
	 * @return  int             < 0 if KO, > 0 if OK
	 */
	public function create(User $user)
	{
		if ($this->id > 0) {
			return $this->update($user);
		}

		$error = 0;
		$this->db->begin();

		$res = $this->createCommon($user);
		if ($res) {
			$this->id = $this->db->last_insert_id($this->table_element);

			$result = $this->call_trigger(strtoupper($this->element).'_CREATE', $user);
			if ($result < 0) {
				$error++;
			} else {
				$this->saveChild($user);
			}
		} else {
			$error++;
			$this->error = $this->db->lasterror();
			$this->errors[] = $this->error;
		}

		if (empty($error)) {
			$this->db->commit();
			return $this->id;
		} else {
			$this->db->rollback();
			return -1;
		}
	}

	/**
	 * Function to delete object in database
	 *
	 * @param   User    $user   user object
	 * @return  int             < 0 if KO, > 0 if OK
	 */
	public function delete(User &$user)
	{
		if ($this->id <= 0) {
			return 0;
		}

		$error = 0;
		$this->db->begin();

		$result = $this->call_trigger(strtoupper($this->element).'_DELETE', $user);
		if ($result < 0) {
			$error++;
		}

		if (!$error) {
			$this->deleteCommon($user);
			if ($this->withChild && !empty($this->childtables)) {
				foreach ($this->childtables as &$childTable) {
					$className = ucfirst($childTable);
					if (!empty($this->{$className})) {
						foreach ($this->{$className} as &$object) {
							$object->delete($user);
						}
					}
				}
			}
		}

		if (empty($error)) {
			$this->db->commit();
			return 1;
		} else {
			$this->error = $this->db->lasterror();
			$this->errors[] = $this->error;
			$this->db->rollback();
			return -1;
		}
	}


	/**
	 * Function to get a formatted date
	 *
	 * @param   string  $field  Attribute to return
	 * @param   string  $format Output date format
	 * @return          string
	 */
	public function getDate($field, $format = '')
	{
		if (empty($this->{$field})) {
			return '';
		} else {
			return dol_print_date($this->{$field}, $format);
		}
	}

	/**
	 * Function to set date in field
	 *
	 * @param   string  $field  field to set
	 * @param   string  $date   formatted date to convert
	 * @return                  mixed
	 */
	public function setDate($field, $date)
	{
		if (empty($date)) {
			$this->{$field} = 0;
		} else {
			require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
			$this->{$field} = dol_stringtotime($date);
		}

		return $this->{$field};
	}


	/**
	 * Function to update current object
	 *
	 * @param   array   $Tab    Array of values
	 * @return                  int
	 */
	public function setValues(&$Tab)
	{
		foreach ($Tab as $key => $value) {
			if ($this->checkFieldType($key, 'date')) {
				$this->setDate($key, $value);
			} elseif ($this->checkFieldType($key, 'float')) {
				$this->{$key} = (double) price2num($value);
			} elseif ($this->checkFieldType($key, 'int')) {
				$this->{$key} = (int) price2num($value);
			} else {
				$this->{$key} = dol_string_nohtmltag($value);
			}
		}

		return 1;
	}
}
