<?PHP

#
#   FILE:  ResourceFactory.php
#
#   METHODS PROVIDED:
#       ResourceFactory()
#           - constructor
#       DuplicateResource($ResourceId)
#           - create duplicate resource and return to caller
#       (SEE ALSO:  ItemFactory.php)
#
#   AUTHOR:  Edward Almasy
#
#   Copyright 2011 Internet Scout Project
#   http://scout.wisc.edu
#

class ResourceFactory extends ItemFactory {

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

    # object constructor
    function ResourceFactory()
    {
        # set up item factory base class
        $this->ItemFactory("Resource", "Resources", "ResourceId");
    }

    # create duplicate resource and return to caller
    function DuplicateResource($ResourceId)
    {
        # create new target resource
        $DstResource = new Resource();

        # load up resource to duplicate
        $SrcResource = new Resource($ResourceId);

        # if resource to duplicate was found
        if ($SrcResource->Status() > 0)
        {
            # for each metadata field
            $Schema = new MetadataSchema();
            $Fields = $Schema->GetFields();
            foreach ($Fields as $Field)
            {
                # skip the cumulative rating field
                if ($Field->Name() != "Cumulative Rating")
                {
                    $NewValue = $SrcResource->GetByField($Field, TRUE);

                    # clear default value from destination resource that is
                    # set when creating a new resource
                    $DstResource->ClearByField($Field);

                    # copy value from source resource to destination resource
                    $DstResource->SetByField($Field, $NewValue);
                }
            }
        }

        # return new resource to caller
        return $DstResource;
    }

    # clear or change specific qualifier for all resources
    function ClearQualifier($ObjectOrId, $NewObjectOrId = NULL)
    {
        # sanitize qualifier ID or retrieve from object
        $QualifierId = is_object($ObjectOrId)
                ?  $ObjectOrId->Id() : intval($ObjectOrId);

        # if new qualifier passed in
        if ($NewObjectOrId !== NULL)
        {
            # sanitize qualifier ID to change to or retrieve it from object
            $NewQualifierIdVal = is_object($NewObjectOrId)
                    ?  $NewObjectOrId->Id() : intval($NewObjectOrId);
        }
        else
        {
            # qualifier should be cleared
            $NewQualifierIdVal = "NULL";
        }

        # for each metadata field
        $Schema = new MetadataSchema();
        $Fields = $Schema->GetFields();
        foreach ($Fields as $Field)
        {
            # if field uses qualifiers and uses item-level qualifiers
            $QualColName = $Field->DBFieldName()."Qualifier";
            if ($Field->UsesQualifiers()
                && $Field->HasItemLevelQualifiers()
                && $this->DB->FieldExists("Resources", $QualColName))
            {
                # set all occurrences to new qualifier value
                $this->DB->Query("UPDATE Resources"
                           ." SET ".$QualColName." = ".$NewQualifierIdVal.""
                           ." WHERE ".$QualColName." = '".$QualifierId."'");
            }
        }

        # clear or change qualifier association with controlled names
        # (NOTE: this should probably be done in a controlled name factory object)
        $this->DB->Query("UPDATE ControlledNames"
                   ." SET QualifierId = ".$NewQualifierIdVal
                   ." WHERE QualifierId = '".$QualifierId."'");

        # clear or change qualifier association with classifications
        # (NOTE: this should probably be done in a classification factory object)
        $this->DB->Query("UPDATE Classifications"
                   ." SET QualifierId = ".$NewQualifierIdVal
                   ." WHERE QualifierId = '".$QualifierId."'");
    }

    # return count of rated resources
    function GetRatedResourceCount()
    {
        $RatedResourceCount = $this->DB->Query(
                "SELECT COUNT(DISTINCT ResourceId) AS ResourceCount "
                ."FROM ResourceRatings", "ResourceCount");
        return $RatedResourceCount;
    }

    # return count of users who have rated resources
    function GetRatedResourceUserCount()
    {
        $RatedResourceCount = $this->DB->Query(
                "SELECT COUNT(DISTINCT UserId) AS UserCount "
                ."FROM ResourceRatings", "UserCount");
        return $RatedResourceCount;
    }

    # return recently released resources
    function GetRecentlyReleasedResources($Count = 10, $Offset = 0, $MaxDaysToGoBack = 90)
    {
        # assume that no resources will be found
        $Resources = array();

        # calculate cutoff date for resources
        $CutoffDate = date("Y-m-d H:i:s", strtotime($MaxDaysToGoBack." days ago"));

        # query for resource IDs
        $this->DB->Query("SELECT ResourceId FROM Resources WHERE"
                ." DateOfRecordRelease > '".$CutoffDate."'"
                ." AND ReleaseFlag = 1"
                ." AND ResourceId >= 0"
                ." ORDER BY DateOfRecordRelease DESC, DateOfRecordCreation DESC"
                ." LIMIT ".intval($Offset).", ".intval($Count));
        $ResourceIds = $this->DB->FetchColumn("ResourceId");

        # for each resource ID found
        foreach ($ResourceIds as $ResourceId)
        {
            # load resource and add to list of found resources
            $Resources[$ResourceId] = new Resource($ResourceId);
        }

        # return found resources to caller
        return $Resources;
    }

    # return resources sorted by specified field
    function GetResourceIdsSortedBy($FieldName, $Ascending = TRUE, $Limit = NULL)
    {
        # assume no resources will be found
        $ResourceIds = array();

        # get field
        $Schema = new MetadataSchema();
        $Field = $Schema->GetFieldByName($FieldName);

        # if field was found
        if ($Field != NULL)
        {
            # construct query based on field type
            switch ($Field->Type())
            {
                case MetadataSchema::MDFTYPE_TEXT:
                case MetadataSchema::MDFTYPE_PARAGRAPH:
                case MetadataSchema::MDFTYPE_URL:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()." IS NOT NULL"
                            ." AND LENGTH(LTRIM(RTRIM(".$Field->DBFieldName()."))) > 0",
                            "ResourceCount");
                    if ($Count > 1)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." ORDER BY ".$Field->DBFieldName()
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;

                case MetadataSchema::MDFTYPE_NUMBER:
                case MetadataSchema::MDFTYPE_TIMESTAMP:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()." IS NOT NULL",
                            "ResourceCount");
                    if ($Count > 1)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." ORDER BY ".$Field->DBFieldName()
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;

                case MetadataSchema::MDFTYPE_DATE:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()."Begin IS NOT NULL",
                            "ResourceCount");
                    if ($Count > 1)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." ORDER BY ".$Field->DBFieldName()."Begin"
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;
            }

            # if appropriate query was found
            if (isset($Query))
            {
                # if limited number of results were requested
                if ($Limit !== NULL)
                {
                    # add limit to query
                    $Query .= " LIMIT ".intval($Limit);
                }

                # perform query and retrieve resource IDs
                $this->DB->Query($Query);
                $ResourceIds = $this->DB->FetchColumn("ResourceId");
            }
        }

        # return resource IDs to caller
        return $ResourceIds;
    }

    # get date/time of last resource modification (returned as Unix timestamp)
    function GetTimestampOfLastResourceModification($OnlyReleasedResources = TRUE)
    {
        $LastChangeDate = $this->DB->Query(
                "SELECT MAX(DateLastModified) AS LastChangeDate"
                ." FROM Resources".($OnlyReleasedResources ? " WHERE ReleaseFlag = 1" : ""),
                "LastChangeDate");
        return ($LastChangeDate ? strtotime($LastChangeDate) : NULL);
    }

    # get list of possible field names for resources
    function GetPossibleFieldNames()
    {
        # retrieve field names from schema
        $FieldNames = array();
        $Schema = new MetadataSchema();
        $Fields = $Schema->GetFields();
        foreach ($Fields as $Field)
        {
            $FieldNames[$Field->Id()] = $Field->Name();
        }

        # return field names to caller
        return $FieldNames;
    }

    # find resources with values that match those specified
    # (index of $ValuesToMatch is field IDs)
    function GetMatchingResources($ValuesToMatch)
    {
        # start out assuming we won't find any resources
        $Resources = array();

        # for each value
        $Schema = new MetadataSchema();
        $Fields = $Schema->GetFields(
                MetadataSchema::MDFTYPE_TEXT |
                MetadataSchema::MDFTYPE_PARAGRAPH |
                MetadataSchema::MDFTYPE_NUMBER | MetadataSchema::MDFTYPE_DATE |
                MetadataSchema::MDFTYPE_TIMESTAMP |
                MetadataSchema::MDFTYPE_FLAG | MetadataSchema::MDFTYPE_URL |
                MetadataSchema::MDFTYPE_POINT);
        $LinkingTerm = "";
        $Condition = "";
        foreach ($ValuesToMatch as $FieldId => $Value)
        {
            # if field can be used for comparison
            if (isset($Fields[$FieldId]))
            {
                # add comparison to condition
                $Condition .= $LinkingTerm.$Fields[$FieldId]->DBFieldName()
                        ." = '".addslashes($Value)."'";
                $LinkingTerm = " AND ";
            }
        }

        # if there were valid conditions
        if (strlen($Condition))
        {
            # build query statment
            $Query = "SELECT ResourceId FROM Resources WHERE ".$Condition;

            # execute query to retrieve matching resource IDs
            $this->DB->Query($Query);
            $ResourceIds = $DB->FetchColumn("ResourceId");

            # retrieve resource objects
            foreach ($ResourceIds as $Id)
            {
                $Resources[$Id] = new Resource($Id);
            }
        }

        # return any resources found to caller
        return $Resources;
    }

    # Functions for keeping per-field resource counts updated:
    function GetResourceCount($FieldId, $Value, $CountType="All")
    {
        if ($this->ResourceCount === NULL)
        {
            $this->DB->Query(
                "SELECT FieldId, ClassName, CountType, Count from ResourceCounts");

            while ($Row = $this->DB->FetchRow())
            {
                $FieldId = $Row["FieldId"];
                $ClassName = $Row["ClassName"];
                $CountType = $Row["CountType"];
                $Count = $Row["Count"];

                $this->ResourceCount[$FieldId][$ClassName][$CountType] = $Count;
            }
        }

        if (time() - $this->ResourceCount[-1]["__LAST_UPDATED__"][""] > 1800 &&
            $this->ResourceCountTaskQueued === FALSE)
        {
            $this->QueueResourceCountUpdate();
        }

        $Schema = new MetadataSchema();
        $Field = $Schema->GetField($FieldId);

        if ($Field->Status() === MetadataSchema::MDFSTAT_OK &&
            ( $Field->Type() === MetadataSchema::MDFTYPE_OPTION ||
              $Field->Type() === MetadataSchema::MDFTYPE_CONTROLLEDNAME ) )
        {
            return isset($this->ResourceCount[$FieldId][$Value][$CountType]) ?
                $this->ResourceCount[$FieldId][$Value][$CountType] :
                0 ;
        }
        else
        {
            return NULL;
        }
    }

    function QueueResourceCountUpdate()
    {
        global $AF;

        # be sure that we're not a gigantic object when the task is queued
        $TmpResourceCount = $this->ResourceCount;
        $this->ResourceCount = NULL;

        $AF->QueueUniqueTask(
            array($this,"UpdateResourceCountCallback"), array());
        $this->ResourceCountTaskQueued = TRUE;
        $this->ResourceCount = $TmpResourceCount;
    }

    function UpdateResourceCountCallback()
    {
        $DB = new Database();
        $DB->Query(
            "CREATE TABLE ResourceCountsNew (FieldId INT, ClassName TEXT, CountType TEXT, Count INT);");

        $Start = microtime(TRUE);

        foreach ($this->ResourceCountConditions as $CountType => $CountCondition)
        {
            $DB->Query(
                "INSERT INTO ResourceCountsNew "
                ."SELECT FieldId, ControlledName AS ClassName,"
                .       "'".$CountType."' AS CountType, Count(ResourceId) AS Count "
                .     "FROM (SELECT * FROM ResourceNameInts WHERE ResourceId IN "
                .           "(SELECT ResourceId FROM Resources "
                .              (($CountCondition!==NULL)?"WHERE ".$CountCondition:"").")) AS T0 "
                .      "JOIN ControlledNames USING(ControlledNameId) GROUP BY ControlledNameId;" );
        }

        $Stop = microtime(TRUE);

        $DB->Query(
            "INSERT INTO ResourceCountsNew VALUES (-1, '__LAST_UPDATED__', '', UNIX_TIMESTAMP()); ");
        $DB->Query(
            "INSERT INTO ResourceCountsNew VALUES (-2, '__UPDATE_RUNTIME__','',".($Stop-$Start).");");
        $DB->Query(
            "RENAME TABLE ResourceCounts TO ResourceCountsOld, ResourceCountsNew TO ResourceCounts; ");
        $DB->Query(
            "DROP TABLE ResourceCountsOld; ");
    }

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

    private $ResourceCount = NULL;
    private $ResourceCountTaskQueued = FALSE;
    private $ResourceCountConditions = array("All" => NULL, "Released" => "ReleaseFlag=1");
}

?>
