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

class SPTRecommender extends Recommender {

    function SPTRecommender()
    {
        # set up recommender configuration values for SPT
        $ItemTableName = "Resources";
        $ItemIdFieldName = "ResourceId";
        $RatingTableName = "ResourceRatings";
        $UserIdFieldName = "UserId";
        $RatingFieldName = "Rating";

        # build field info from SPT metadata schema
        $this->Schema = new MetadataSchema();
        $Fields = $this->Schema->GetFields();
        foreach ($Fields as $Field)
        {
            if ($Field->Enabled() && $Field->IncludeInKeywordSearch())
            {
                $FieldName = $Field->Name();
                $FieldInfo[$FieldName]["DBFieldName"] = $Field->DBFieldName();
                $FieldInfo[$FieldName]["Weight"] = $Field->SearchWeight();
                switch ($Field->Type())
                {
                case MetadataSchema::MDFTYPE_TEXT:
                case MetadataSchema::MDFTYPE_PARAGRAPH:
                case MetadataSchema::MDFTYPE_USER:
                case MetadataSchema::MDFTYPE_URL:
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_TEXT;
                    break;

                case MetadataSchema::MDFTYPE_TREE:
                case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
                case MetadataSchema::MDFTYPE_OPTION:
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_TEXT;
                    break;

                case MetadataSchema::MDFTYPE_NUMBER:
                case MetadataSchema::MDFTYPE_FLAG:
                        $FieldInfo[$FieldName]["FieldType"] =
                            Recommender::CONTENTFIELDTYPE_NUMERIC;
                        break;

                case MetadataSchema::MDFTYPE_DATE:
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_DATERANGE;
                    break;

                case MetadataSchema::MDFTYPE_TIMESTAMP:
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_DATE;
                    break;

                case MetadataSchema::MDFTYPE_IMAGE:
                    # (for images we use their alt text)
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_TEXT;
                    break;

                case MetadataSchema::MDFTYPE_FILE:
                    # (for files we use the file name)
                    $FieldInfo[$FieldName]["FieldType"] =
                        Recommender::CONTENTFIELDTYPE_TEXT;
                    break;
                }
            }
        }

        # create our own schema object and tell it to cache values
        $this->Schema = new MetadataSchema();
        $this->Schema->CacheData(TRUE);

        # create a database connection for recommender to use
        $DB = new Database();

        # pass configuration info to real recommender object
        $this->Recommender($DB, $ItemTableName, $RatingTableName,
                $ItemIdFieldName, $UserIdFieldName, $RatingFieldName,
                $FieldInfo);
    }

    # overloaded version of method to retrieve field values from DB
    function GetFieldValue($ItemId, $FieldName)
    {
        static $Resources;

        # if resource not already loaded
        if (!isset($Resources[$ItemId]))
        {
            # get resource object
            $Resources[$ItemId] = new Resource($ItemId);

            # if cached resource limit exceeded
            if (count($Resources) > 100)
            {
                # dump oldest resource
                reset($Resources);
                list($DumpedItemId, $DumpedResources) = each($Resources);
                unset($Resources[$DumpedItemId]);
            }
        }

        # retrieve field value from resource object and return to caller
        $FieldValue = $Resources[$ItemId]->Get($FieldName);
        return $FieldValue;
    }

    function QueueUpdateForItem($ItemId,
            $TaskPriority = ApplicationFramework::PRIORITY_BACKGROUND)
    {
        $Item = new Resource($ItemId);
        $TaskDescription = "Update recommender data for"
                ." <a href=\"r".$ItemId."\"><i>"
                .$Item->GetMapped("Title")."</i></a>";
        $GLOBALS["AF"]->QueueUniqueTask(array(__CLASS__, "RunUpdateForItem"),
                array(intval($ItemId), 0), $TaskPriority);
    }

    static function RunUpdateForItem($SourceItemId, $StartingIndex)
    {
        # check that resource still exists
        $RFactory = new ResourceFactory();
        if (!$RFactory->ItemExists($SourceItemId)) {  return;  }

        # load recommender engine
        static $Recommender;
        if (!isset($Recommender)) {  $Recommender = new SPTRecommender();  }

        # if starting update for source item
        if ($StartingIndex == 0)
        {
            # clear data for item
            $Recommender->DropItem($SourceItemId);
        }

        # load array of item IDs
        $TargetItemIds = $Recommender->GetItemIds();
        $TargetCount = count($TargetItemIds);

        # while not last item ID and not out of time
        for ($Index = $StartingIndex;  $Index < $TargetCount;  $Index++)
        {
            # if target ID points to non-temporary entry
            if ($TargetItemIds[$Index] >= 0)
            {
                # update correlation for source item and current item
                $StartTime = microtime(TRUE);
                $Recommender->UpdateContentCorrelation(
                        $SourceItemId, $TargetItemIds[$Index]);
                $ExecutionTime = microtime(TRUE) - $StartTime;

                # bail out if out of memory or not enough time for another update
                if (($GLOBALS["AF"]->GetSecondsBeforeTimeout() < ($ExecutionTime * 2))
                        || ($GLOBALS["AF"]->GetFreeMemory() < 8000000))
                {
                    break;
                }
            }
        }

        # if all correlations completed for source item
        if ($Index >= $TargetCount)
        {
            # periodically prune correlations if enough time remaining
            if (($GLOBALS["AF"]->GetSecondsBeforeTimeout() > 20)
                    && (rand(1, 10) == 1))
            {
                $Recommender->PruneCorrelations();
            }
        }
        else
        {
            # requeue updates for remaining items
            $Item = new Resource($SourceItemId);
            $TaskDescription = "Update recommender data for"
                    ." <a href=\"r".$SourceItemId."\"><i>"
                    .$Item->GetMapped("Title")."</i></a>";
            $GLOBALS["AF"]->QueueUniqueTask(array(__CLASS__, "RunUpdateForItem"),
                    array((int)$SourceItemId, $Index),
                    ApplicationFramework::PRIORITY_LOW);
        }
    }


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

    private $Schema;

}
