<?PHP
#
#   FILE:  Item.php
#
#   Part of the ScoutLib application support library
#   Copyright 2016 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu
#

/**
* Common base class for persistent items store in database.
*/
abstract class Item
{

    # ---- PUBLIC INTERFACE --------------------------------------------------

    /**
    * Constructor, used to load existing items.  To create new items, child
    * classes should implement a static Create() method.
    * @param mixed $Id ID of item to load, in a form resolvable by
    *       GetCanonicalId().
    * @see Create()
    * @see GetCanonicalId()
    */
    public function __construct($Id)
    {
        # set up database access values
        $ClassName = get_class($this);
        static::SetDatabaseAccessValues($ClassName);
        $this->ItemIdColumnName = self::$ItemIdColumnNames[$ClassName];
        $this->ItemNameColumnName = self::$ItemNameColumnNames[$ClassName];
        $this->ItemTableName = self::$ItemTableNames[$ClassName];

        # normalize item ID
        $this->Id = static::GetCanonicalId($Id);

        # load item info from database
        $this->DB = new Database();
        $this->DB->Query("SELECT * FROM `".$this->ItemTableName."`"
                ." WHERE `".$this->ItemIdColumnName."` = "
                .intval($this->Id));
        $this->ValueCache = $this->DB->FetchRow();

        # error out if item not found in database
        if ($this->ValueCache === FALSE)
        {
            throw new Exception("Attempt to load ".$ClassName
                    ." with unknown ID (".$Id.").");
        }
    }

    /**
    * Destroy item.  Item object should no longer be used after this call.
    */
    public function Delete()
    {
        # delete item from database
        $this->DB->Query("DELETE FROM `".$this->ItemTableName."`"
                ." WHERE `".$this->ItemIdColumnName."` = ".intval($this->Id));
    }

    /**
    * Get item ID.
    * @return int Canonical item ID.
    */
    public function Id()
    {
        return $this->Id;
    }

    /**
    * Normalize item ID to canonical form.
    * @param mixed $Id ID to normalize.
    * @return int Canonical ID.
    */
    public static function GetCanonicalId($Id)
    {
        return $Id;
    }

    /**
    * Check whether an item exists with the specified ID.  This only checks
    * whether there is an entry for an item with the specified ID in the
    * database -- it does not check anything else (e.g. the type of the item).
    * @param int $Id ID to check.
    * @return bool TRUE if item exists with ID, otherwise FALSE.
    */
    public static function ItemExists($Id)
    {
        # check for NULL ID
        if ($Id === NULL)
        {
            return FALSE;
        }

        # set up database access values
        $ClassName = get_called_class();
        static::SetDatabaseAccessValues($ClassName);

        # build database query to check for item
        $Query = "SELECT COUNT(*) AS ItemCount"
                ." FROM ".self::$ItemTableNames[$ClassName]
                ." WHERE ".self::$ItemIdColumnNames[$ClassName]." = ".intval($Id);

        # check for item and return result to caller
        $DB = new Database();
        $ItemCount = $DB->Query($Query, "ItemCount");
        return ($ItemCount > 0) ? TRUE : FALSE;
    }


    # ---- PRIVATE INTERFACE -------------------------------------------------

    protected $DB;
    protected $Id;
    protected $ItemIdColumnName;
    protected $ItemNameColumnName;
    protected $ItemTableName;
    protected $ValueCache = array();

    static protected $ItemIdColumnNames;
    static protected $ItemNameColumnNames;
    static protected $ItemTableNames;

    /**
    * Create a new item, using specified initial database values.
    * @param array $Values Values to set database columns to for
    *       new item, with column names for the index.
    * @return object Newly-created item.
    */
    protected static function CreateWithValues($Values)
    {
        # set up database access values
        $ClassName = get_called_class();
        static::SetDatabaseAccessValues($ClassName);

        # set up query to add item to database
        $Query = "INSERT INTO `".self::$ItemTableNames[$ClassName]."`";

        # add initial values to query if supplied
        if (count($Values))
        {
            $Query .= " SET ";
            foreach ($Values as $Column => $Value)
            {
                $Assignments[] = "`".$Column."` = '".addslashes($Value)."'";
            }
            $Query .= implode(", ", $Assignments);
        }

        # add item to database
        $DB = new Database();
        $DB->Query($Query);

        # retrieve ID for newly-created item
        $NewItemId = $DB->LastInsertId();

        # create item object
        $NewItem = new $ClassName($NewItemId);

        # return new item object to caller
        return $NewItem;
    }

    /**
    * Set the database access values (table name, ID column name, name column
    * name) for specified class.  This may be overridden in a child class, if
    * different values are needed.
    * @param string $ClassName Class to set values for.
    */
    static protected function SetDatabaseAccessValues($ClassName)
    {
        if (!isset(self::$ItemIdColumnNames[$ClassName]))
        {
            self::$ItemIdColumnNames[$ClassName] = $ClassName."Id";
            self::$ItemNameColumnNames[$ClassName] = $ClassName."Name";
            self::$ItemTableNames[$ClassName] = StdLib::Pluralize($ClassName);
        }
    }

    /**
    * Convenience function to supply parameters to Database->UpdateValue().
    * @param string $ColumnName Name of database column.
    * @param string $NewValue New value for field.  (OPTIONAL)
    * @return string Return value from Database::UpdateValue().
    * @see Database::UpdateValue()
    */
    protected function UpdateValue($ColumnName, $NewValue = DB_NOVALUE)
    {
        return $this->DB->UpdateValue($this->ItemTableName, $ColumnName, $NewValue,
                               "`".$this->ItemIdColumnName."` = ".intval($this->Id),
                               $this->ValueCache);
    }
}
