<?PHP
#
#   FILE:  ControlledName.php
#
#   Part of the Collection Workflow Integration System (CWIS)
#   Copyright 2001-2016 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu/cwis/
#

/**
* Metadata type representing non-hierarchical controlled vocabulary values.
* Hierarchical controlled vocabularies should use Classification.
*/
class ControlledName
{

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

    /** @name Status Codes (set by constructor and retrieved via Status()) */ /*@{*/
    /** Successful execution. */
    const STATUS_OK = 0;
    /** No ControlledName exists with specified ID. */
    const STATUS_INVALID_ID = 1;
    /** ControlledName already exists with this term. */
    const STATUS_EXISTS = 2;
    /*@}*/

    /**
    * Class constructor.  This can be used both to access an existing
    * controlled vocabulary term or to add a new term.  For existing
    * terms pass in just the ControlledName ID.  To create a new term
    * pass in NULL for the ID, and specify the term, the metadata field
    * ID, and the qualifier ID (if any) and variant name (if any).
    * @param int $NameId ControlledName ID (NULL if creating new).
    * @param string $Name New controlled vocabulary term.  (OPTIONAL)
    * @param int $FieldId ID of MetadataField for new term.  (OPTIONAL)
    * @param int $QualifierId ID of Qualifier for new term (if any).  (OPTIONAL)
    * @param string $VariantName Variant names for new term (if any).  (OPTIONAL)
    */
    public function __construct($NameId, $Name = NULL, $FieldId = NULL,
                    $QualifierId = "NULL", $VariantName = NULL)
    {
        # assume everything will turn out okay
        $this->ErrorStatus = self::STATUS_OK;

        # create DB handle for our use
        $this->DB = new Database();
        $DB =& $this->DB;

        # remove whitespace padding
        $Name = trim($Name);
        $VariantName = trim($VariantName);

        # look for passed in name and type
        if (!empty($Name) && !empty($FieldId))
        {
            $DB->Query("SELECT * FROM ControlledNames".
                   " WHERE ControlledName = \"".addslashes($Name)."\"".
                   " AND FieldId = ".intval($FieldId));

            while ($this->DBFields = $DB->FetchRow())
            {
                # this controlled name already exists
                if ($this->DBFields["ControlledName"] == $Name)
                {
                    $this->ErrorStatus = self::STATUS_EXISTS;
                    $NameId = $this->DBFields["ControlledNameId"];

                    # cache the variant name separately
                    $VN = $DB->Query("SELECT VariantName FROM VariantNames".
                        " WHERE ControlledNameId = ".
                        $this->DBFields["ControlledNameId"], "VariantName");

                    $this->DBFields["VariantName"] = $VN;
                    break;
                }
            }
            # controlled name not found, create it
            if ($this->ErrorStatus == self::STATUS_OK)
            {
                # add new controlled name
                $DB->Query("INSERT INTO ControlledNames ".
                        "(FieldId, ControlledName, QualifierId)".
                        " VALUES (".intval($FieldId).", '".addslashes($Name)
                        ."', ".intval($QualifierId).")");

                # get name ID  for new controlled name
                $NameId = $DB->LastInsertId();

                # check for Variant
                if (!empty($VariantName))
                {
                    $DB->Query("INSERT INTO VariantNames ".
                        "(ControlledNameId, VariantName) ".
                        "VALUES (".intval($NameId).", '"
                        .addslashes($VariantName)."') ");
                }
            }
        }
        # Name Id passed in, look it up
        if (!empty($NameId) && $NameId != -1)
        {
            $DB->Query("SELECT * FROM ControlledNames".
                   " WHERE ControlledNameId = ".intval($NameId));
            $this->DBFields = $DB->FetchRow();

            # cache the variant name separately
            $VN = $DB->Query("SELECT VariantName FROM VariantNames".
                   " WHERE ControlledNameId = ".intval($NameId), "VariantName");

            $this->DBFields["VariantName"] = $VN;
        }

        # save supplied or generated controlled name ID
        $this->Id = intval($NameId);

        # set error status if controlled name info not loaded
        if (!array_key_exists("ControlledNameId", $this->DBFields)
                || ($this->DBFields["ControlledNameId"] != $this->Id))
        {
            $this->ErrorStatus = self::STATUS_INVALID_ID;
        }
    }

    /**
    * Check success of constructor.
    * @return Status code for constructor.
    */
    public function Status()
    {
        return $this->ErrorStatus;
    }

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

    /**
    * Get or set the controlled vocabulary term.
    * @param string $NewValue New value for term.  (OPTIONAL)
    * @return Controlled vocabulary term.
    */
    public function Name($NewValue = DB_NOVALUE)
    {
        return $this->UpdateValue("ControlledName", $NewValue);
    }

    /**
    * Get or set any variant terms for this controlled name .
    * @param string $NewValue New value for variant terms.  (OPTIONAL)
    * @return Variant term.
    */
    public function VariantName($NewValue = DB_NOVALUE)
    {
        return $this->UpdateVariantValue("VariantName", $NewValue);
    }

    /**
    * Get or set the MetadataField associated with this term.
    * @param int $NewValue ID of new MetadataField.  (OPTIONAL)
    * @return ID of associated MetadataField.
    */
    public function FieldId($NewValue = DB_NOVALUE)
    {
        return $this->UpdateValue("FieldId", $NewValue);
    }

    /**
    * Get or set the Qualifier associated with this term via ID.
    * @param int $NewValue ID of new Qualifier.  (OPTIONAL)
    * @return ID of currently associated Qualifier.
    */
    public function QualifierId($NewValue = DB_NOVALUE)
    {
        return $this->UpdateValue("QualifierId", $NewValue);
    }

    /**
    * Get or set the controlled name variant.
    * @param string $NewValue New value of the controlled name variant.  (OPTIONAL)
    * @return the current value of the controlled name variant.
    */
    public function Variant($NewValue = DB_NOVALUE)
    {
        return $this->VariantName($NewValue);
    }

    /**
    * Get or set the Qualifier associated with this term via object.
    * @param Qualifier $NewValue New Qualifier.  (OPTIONAL)
    * @return Currently associated Qualifier.
    */
    public function Qualifier($NewValue = DB_NOVALUE)
    {
        # if new qualifier supplied
        if ($NewValue !== DB_NOVALUE)
        {
            # set new qualifier ID
            $this->QualifierId($NewValue->Id());

            # use new qualifier for return value
            $Qualifier = $NewValue;
        }
        else
        {
            # if qualifier is available
            if ($this->QualifierId() !== NULL)
            {
                # create qualifier object using stored ID
                $Qualifier = new Qualifier($this->QualifierId());

                # if ID was zero and no name available for qualifieR
                # (needed because some controlled name records in DB
                #       have 0 instead of NULL when no controlled name assigned)
                # (NOTE:  this is problematic if there is a qualifier with an
                #       ID value of 0!!!)
                if (($this->QualifierId() == 0) && !strlen($Qualifier->Name()))
                {
                    # return NULL to indicate no qualifier
                    $Qualifier = NULL;
                }
            }
            else
            {
                # return NULL to indicate no qualifier
                $Qualifier = NULL;
            }
        }

        # return qualifier to caller
        return $Qualifier;
    }

    /**
    * Check if the given controlled name already exists for a given field ID.
    * @param string $ControlledName Name to search for.
    * @param int $FieldId ID of field to search.
    * @return array Matching controlled name IDs, if any.
    */
    static public function SearchForControlledName($ControlledName, $FieldId)
    {
        $Database = new Database();

        # query for the controlled name
        $Database->Query("
            SELECT ControlledNameId FROM
            ControlledNames WHERE FieldId='".addslashes($FieldId)."'
            AND ControlledName='".addslashes($ControlledName)."'");

        # return the controlled name IDs found, if any
        return $Database->FetchColumn("ControlledNameId");
    }

    /**
    * See if ControlledName is currently associated with any Resources.
    * @return TRUE if associated with at least one Resource, otherwise FALSE.
    */
    public function InUse()
    {
        return $this->DB->Query("SELECT COUNT(*) AS Count FROM ".
                "ResourceNameInts WHERE ControlledNameId = ".$this->Id, "Count");
    }

    /**
    * Get resourceIds associated with this ControlledName.
    * @return array of ResourceIds.
    */
    public function GetAssociatedResources()
    {
        $this->DB->Query(
            "SELECT ResourceId FROM ResourceNameInts "
            ."WHERE ControlledNameId = ".$this->Id);

        return $this->DB->FetchColumn("ResourceId");
    }

    /**
    * Change all currently associated Resources to be instead associated with
    * another ControlledName.
    * @param int $NewNameId ID of ControlledName to remap resources to.
    */
    public function RemapTo($NewNameId)
    {
        # Get a list of resources associated with the new name
        $this->DB->Query("SELECT ResourceId FROM ResourceNameInts "
                         ."WHERE ControlledNameId = ".intval($NewNameId));
        $NewNameResources = array();
        while ($Row = $this->DB->FetchRow())
        {
            $NewNameResources[$Row["ResourceId"]]=1;
        }

        # Get a list of resources associated with the old name
        $this->DB->Query("SELECT ResourceId FROM ResourceNameInts "
                         ."WHERE ControlledNameId = ".intval($this->Id));
        $OldNameResources = array();
        while ($Row = $this->DB->FetchRow())
        {
            $OldNameResources[]= $Row["ResourceId"];
        }

        # Foreach of the old name resources, check to see if it's already
        # associated with the new name.  If not, associate it.
        foreach ($OldNameResources as $ResourceId)
        {
            if (!isset($NewNameResources[$ResourceId]))
            {
                $this->DB->Query("INSERT INTO ResourceNameInts "
                                 ."(ResourceId, ControlledNameId) VALUES "
                                 ."(".intval($ResourceId).",".intval($NewNameId).")");
            }
        }

        # Clear out all the associations to the old name
        $this->DB->Query("DELETE FROM ResourceNameInts WHERE ControlledNameId = "
                .intval($this->Id));
    }

    /**
    * Update the LastAssigned timestamp for this classification.
    */
    public function UpdateLastAssigned()
    {
        $this->DB->Query("UPDATE ControlledNames SET LastAssigned=NOW() "
                         ."WHERE ControlledNameId=".intval($this->Id));
    }

    /**
    * Remove ControlledName (and any accompanying associations from database.
    * This must be the last use of this object.
    * @param bool $DeleteIfHasResources Remove ControlledName even if Resources are
    *       currently associated with it.  (OPTIONAL, defaults to FALSE)
    */
    public function Delete($DeleteIfHasResources = FALSE)
    {
        $DB =& $this->DB;

        if ($DeleteIfHasResources || !$this->InUse())
        {
            # delete this controlled name
            $DB->Query("DELETE FROM ControlledNames WHERE ControlledNameId=".
                $this->Id);

            # delete associated variant name
            $DB->Query("DELETE FROM VariantNames WHERE ControlledNameId=".
                $this->Id);

            if ($DeleteIfHasResources)
            {
                $DB->Query("DELETE FROM ResourceNameInts WHERE ".
                   "ControlledNameId=".$this->Id);
            }
        }
    }

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

    private $DB;
    private $DBFields;
    private $Id;
    private $ErrorStatus;

    /**
    * Convenience function to supply parameters to Database->UpdateValue().
    * @param string $FieldName Name of field to update/read.
    * @param mixed $NewValue New value to save in field.
    * @return mixed Current value for field.
    * @see Database::UpdateValue()
    */
    private function UpdateValue($FieldName, $NewValue)
    {
        return $this->DB->UpdateValue("ControlledNames", $FieldName,
                $NewValue, "ControlledNameId = ".$this->Id,
                $this->DBFields, TRUE);
    }

    /**
    * Convenience function for saving and retrieving variant names.
    * @param string $FieldName Name of field to update/read.
    * @param mixed $NewValue New value to save in field.
    * @return mixed Current value for field.
    * @see Database::UpdateValue()
    */
    private function UpdateVariantValue($FieldName, $NewValue)
    {
        if (!empty($NewValue))
        {
            # see if variant name exists for the controlled Name
            $this->DB->Query("SELECT * from VariantNames WHERE ".
                        "ControlledNameId = ".$this->Id);

            # variant name exists so do an update
            if ($this->DB->NumRowsSelected() > 0)
            {
                return $this->DB->UpdateValue("VariantNames",
                            $FieldName, $NewValue,
                            "ControlledNameId = ".$this->Id,
                            $this->DBFields, TRUE);
            }
            # no variant name so do an insert
            else if ($NewValue != DB_NOVALUE)
            {
                $this->DB->Query("INSERT into VariantNames ".
                        "(VariantName, ControlledNameId) VALUES ".
                        "('".addslashes($NewValue)."', ".$this->Id.")");
            }
        }
        # delete variant name
        else
        {
            $this->DB->Query("Delete from VariantNames where ".
                "ControlledNameId = ".$this->Id);
        }
    }
}
