/*
    TCFbEOutputCache.cpp    June 6, 2004.

    Copyright (C) 2003-2005 CFbE Research Group,
    Software Engineering Laboratory,
    Graduate School of Information Science,
    Nara Institute of Science and Technology,
    All rights reserved.

    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 2, (at your option) or
    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 GNU Emacs; see the file COPYING.  If not, write to the
    Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/
//---------------------------------------------------------------------------
#pragma hdrstop

#include "TCFbEOutputCache.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
// コンストラクタ
__fastcall TCFbEOutputCache::TCFbEOutputCache(TCFbEDocfbeCommandLineParser* Parser, TCFbEDocfbeDataLoader* DataLoader, bool IsVerbose)
{                                    
    // 変数の初期化
    this->Parser = Parser;
    this->DataLoader = DataLoader;
    this->FResultTable = new TCFbERowColumnCachedTable();
    this->FNeighborsList = new TObjectList();
    this->FDistributionList = new TObjectList();

    try {
        TCFbEToolkit::GetToolkit()->SetApplicationPriority(tpLower);

        if (IsVerbose) {
            fprintf(stderr, "(3/4) Estimation is started with the following algorithms.\n");
            fprintf(stderr, "  %s\n", Parser->SimilarityComputationName);
            fprintf(stderr, "  %s\n", Parser->PredictionName);
            if (Parser->NeighborsSize > 0) {
                fprintf(stderr, "  ( Neighbors size is %d )\n", Parser->NeighborsSize);
            } else {
                fprintf(stderr, "  ( All neighbors are used )\n");
            }

            fprintf(stderr, "    ..... completed:  0.00\%");
        }

        // ResultTable の準備
        for (int j = 0; j < this->DataLoader->EstimatingData->GetNumberOfColumns(); j++) {
            this->FResultTable->AddColumn(this->DataLoader->EstimatingData->GetColumnLabelList()->Strings[j].c_str());
        }
        for (int i = 0; i < this->DataLoader->EstimatingData->GetNumberOfRows(); i++) {
            this->FResultTable->AddRow(this->DataLoader->EstimatingData->GetRowLabelList()->Strings[i].c_str());
        }

        // 予測すべき列の Cell を Enabled = true にする
        if (Parser->TargetColumnLabelList->Count > 0) {
            if (Parser->TargetColumnLabelList->Text.Trim() != "*") {    // 指定列を予測
                for (int i = 0; i < this->DataLoader->EstimatingData->GetNumberOfRows(); i++) {
                    for (int k = 0; k < Parser->TargetColumnLabelList->Count; k++) {
                        int j = this->FResultTable->GetColumnLabelList()->IndexOf(Parser->TargetColumnLabelList->Strings[k]);
                        this->FResultTable->SetValueByIndex(i, j, 0.0);
                    }
                }
            } else {    // 全ての列が予測対象
                for (int i = 0; i < this->DataLoader->EstimatingData->GetNumberOfRows(); i++) {
                    for (int j = 0; j < this->DataLoader->EstimatingData->GetNumberOfColumns(); j++) {
                        this->FResultTable->SetValueByIndex(i, j, 0.0);
                    }
                }
            }
        } else if (Parser->TargetColumnLabelList->Count == 0) {    // Estimating Data の Enabled = false の列を予測
            for (int i = 0; i < this->DataLoader->EstimatingData->GetNumberOfRows(); i++) {
                for (int j = 0; j < this->DataLoader->EstimatingData->GetColumnLabelList()->Count; j++) {
                    if (!this->DataLoader->EstimatingData->GetEnabledByIndex(i, j)) {
                        this->FResultTable->SetValueByIndex(i, j, 0.0);
                    }
                }
            }
        }

        // 予測の開始
        this->DataLoader->LearningData->PrepareCF(Parser->ValueBase, Parser->AlgorithmBase, Parser->CreateSimilarityComputationAlgorithm(Parser->SimilarityComputationName), Parser->CreatePredictionAlgorithm(Parser->PredictionName), Parser->NeighborsSize, Parser->DefaultValueEnabled, Parser->DefaultValue, Parser->InverseCaseFrequencyEnabled, Parser->CaseAmplifier, Parser->CreateSimilarityComputationAlgorithm(Parser->VWSimilarityComputationName), Parser->CreateSimilarityComputationAlgorithm(Parser->VWPredictionName));

        if (Parser->AlgorithmBase == abColumn) {   // Item-based アルゴリズム
            for (int j = 0; j < this->FResultTable->GetNumberOfColumns(); j++) {
                int TargetColumnIndex = this->GetTargetColumnIndex(this->DataLoader->LearningData, this->FResultTable, j);
                for (int i = 0; i < this->FResultTable->GetNumberOfRows(); i++) {
                    TCFbECell* Result = this->FResultTable->GetCellByIndex(i, j);
                    if (Result->GetEnabled()) {
                        int TargetRowIndex = this->GetTargetRowIndex(this->DataLoader->LearningData, this->DataLoader->EstimatingData, i);
                        this->PredictionBody(Result, this->DataLoader->LearningData, TargetRowIndex, TargetColumnIndex);
                        if (this->FNeighborsList->Count <= i) { // NearestNeighbors と類似度の分布を調べる（調べるべき最初の Column についてだけ調べる
                            this->GetNeighborsInformation(this->FNeighborsList, this->FDistributionList, this->DataLoader->LearningData, TargetRowIndex, TargetColumnIndex, Parser->AlgorithmBase, Parser->NeighborsSize);
                        }
                        if (this->DataLoader->EstimatingData != this->DataLoader->LearningData) {
                            this->DataLoader->LearningData->DeleteRow(this->DataLoader->LearningData->GetRowLabelList()->Strings[TargetRowIndex].c_str());
                        }
                    }
                }
                if (IsVerbose) {
                    fprintf(stderr, "\b\b\b\b\b\b%5.2f\%", (double)j / (double)this->DataLoader->EstimatingData->GetNumberOfColumns() * 100.0);
                }
            }
        } else {    // User-based アルゴリズム
            for (int i = 0; i < this->FResultTable->GetNumberOfRows(); i++) {
                int TargetRowIndex = this->GetTargetRowIndex(this->DataLoader->LearningData, this->DataLoader->EstimatingData, i);
                for (int j = 0; j < this->FResultTable->GetNumberOfColumns(); j++) {
                    TCFbECell* Result = this->FResultTable->GetCellByIndex(i, j);
                    if (Result->GetEnabled()) {
                        int TargetColumnIndex = this->GetTargetColumnIndex(this->DataLoader->LearningData, this->FResultTable, j);
                        this->PredictionBody(Result, this->DataLoader->LearningData, TargetRowIndex, TargetColumnIndex);
                        if (this->FNeighborsList->Count <= i) { // NearestNeighbors と類似度の分布を調べる（調べるべき最初の Column についてだけ調べる
                            this->GetNeighborsInformation(this->FNeighborsList, this->FDistributionList, this->DataLoader->LearningData, TargetRowIndex, TargetColumnIndex, Parser->AlgorithmBase, Parser->NeighborsSize);
                        }
                    }
                }
                if (this->DataLoader->EstimatingData != this->DataLoader->LearningData) {
                    this->DataLoader->LearningData->DeleteRow(this->DataLoader->LearningData->GetRowLabelList()->Strings[TargetRowIndex].c_str());
                }
                if (IsVerbose) {
                    fprintf(stderr, "\b\b\b\b\b\b%5.2f\%", (double)i / (double)this->DataLoader->EstimatingData->GetNumberOfRows() * 100.0);
                }
            }
        }

        // j 列目の値が予測されたかどうかを調べておく
        this->FIsResultedColumn.Length = this->FResultTable->GetNumberOfColumns();
        for (int j = 0; j < this->FResultTable->GetNumberOfColumns(); j++) {
            this->FIsResultedColumn[j] = false;
            for (int i = 0; i < this->FResultTable->GetNumberOfRows(); i++) {
                if (this->FResultTable->GetCellByIndex(i, j)->GetEnabled()) {
                    this->FIsResultedColumn[j] = true;
                    break;
                }
            }
        }

        if (IsVerbose) {
            fprintf(stderr, "\b\b\b\b\b\b100.00\%\n\n");
        }

        TCFbEToolkit::GetToolkit()->SetApplicationPriority(tpNormal);
        
    } catch (...) {
        TCFbEToolkit::GetToolkit()->SetApplicationPriority(tpNormal);

        delete this->FResultTable;
        delete this->FNeighborsList;
        delete this->FDistributionList;
        throw;
    }

    return;
}

//---------------------------------------------------------------------------
__fastcall TCFbEOutputCache::~TCFbEOutputCache()
{
    this->FIsResultedColumn.Length = 0;
    delete this->FDistributionList;
    delete this->FNeighborsList;
    delete this->FResultTable;
}

//---------------------------------------------------------------------------
AnsiString __fastcall TCFbEOutputCache::GetResultRowLabel(int RowIndex)
{
    return this->FResultTable->GetRowLabelList()->Strings[RowIndex];
}

//---------------------------------------------------------------------------
AnsiString __fastcall TCFbEOutputCache::GetResultColumnLabel(int ColumnIndex)
{
    return this->FResultTable->GetColumnLabelList()->Strings[ColumnIndex];
}
//---------------------------------------------------------------------------
// 以下，protected メソッドの定義
//---------------------------------------------------------------------------
// DataSet における TargetRowIndex 行目の行に対する類似度の分布を StoredList に格納して返す
void __fastcall TCFbEOutputCache::CountDistribution(TCFbEDataSetEx* DataSet, int TargetRowIndex, int TargetColumnIndex, TList* StoredList)
{
    StoredList->Clear();
    for (int k = 0; k < 20; k++) {
        StoredList->Add((void*)0);
    }

    double MaxSimilarity = 0.0;
    double MinSimilarity = 0.0;
    bool First = true;
    for (int i = 0; i < DataSet->GetSimilarityComputationTable()->GetNumberOfRows(); i++) {
        if (i != TargetRowIndex) {
            double Similarity = DataSet->GetSimilarityByIndex(TargetRowIndex, i, TargetColumnIndex);
            if (First || (Similarity > MaxSimilarity)) {
                MaxSimilarity = Similarity;
            }
            if (First || (Similarity < MinSimilarity)) {
                MinSimilarity = Similarity;
                First = false;
            }
        }
    }

    if ((MaxSimilarity > 1.0) || (MinSimilarity < -1.0)) {
        for (int i = 0; i < DataSet->GetSimilarityComputationTable()->GetNumberOfRows(); i++) {
            if (i != TargetRowIndex) {
                double NormalizedSimilarity = (DataSet->GetSimilarityByIndex(TargetRowIndex, i, TargetColumnIndex) - MinSimilarity) / (MaxSimilarity - MinSimilarity);
                int Index = Floor(NormalizedSimilarity * 20.0 + 0.5);
                if (Index >= 20) {
                    Index = 19;
                }
                StoredList->Items[Index] = (void*)(1 + (int)StoredList->Items[Index]);
            }
        }
    } else {
        for (int i = 0; i < DataSet->GetSimilarityComputationTable()->GetNumberOfRows(); i++) {
            if (i != TargetRowIndex) {
                int Index = Floor((DataSet->GetSimilarityByIndex(TargetRowIndex, i, TargetColumnIndex) + 1.0) * 10.0);
                if (Index >= 20) {
                    Index = 19;
                }
                StoredList->Items[Index] = (void*)(1 + (int)StoredList->Items[Index]);
            }
        }
    }


    return;
}

//---------------------------------------------------------------------------
// コンストラクタでの計算で用いるメソッド，Result に予測値を設定する
void __fastcall TCFbEOutputCache::PredictionBody(TCFbECell* Result, TCFbEDataSetEx* LearningData, int TargetRowIndex, int TargetColumnIndex)
{
    LearningData->TemporallyUnable(TargetRowIndex, TargetColumnIndex);    // 予測対象のメトリクスを一時的に Enabled = false にする
    try {
        Result->SetValue(LearningData->GetPredictedValueByIndex(TargetRowIndex, TargetColumnIndex));
        Result->SetRank(1.0);    // 成功のときは Rank = 1.0 とする
    } catch (...) {
        Result->SetEnabled(false);
        Result->SetRank(0.0);    // 失敗のときは Rank = 0.0 とする
    }
    LearningData->RecoverEnabled();
}

//---------------------------------------------------------------------------
// コンストラクタでの計算で用いるメソッド，NearestNeighbors と類似度の分布を調べる
void __fastcall TCFbEOutputCache::GetNeighborsInformation(TObjectList* NeighborsList, TObjectList* DistributionList, TCFbEDataSetEx* LearningData, int TargetRowIndex, int TargetColumnIndex, TCFbEAlgorithmBase AlgorithmBase, int NeighborsSize)
{
    TObjectList* NeighborList = new TObjectList;
    TList* Distribution = new TList();
    try {
        if (AlgorithmBase == abColumn) {    // アイテムベースのとき
            LearningData->GetNearestNeighbors(TargetColumnIndex, TargetRowIndex, NeighborsSize, NeighborList);
            this->CountDistribution(this->DataLoader->LearningData, TargetColumnIndex, TargetRowIndex, Distribution);
        } else {
            LearningData->GetNearestNeighbors(TargetRowIndex, TargetColumnIndex, NeighborsSize, NeighborList);
            this->CountDistribution(this->DataLoader->LearningData, TargetRowIndex, TargetColumnIndex, Distribution);
        }
    } catch (...) {    // 失敗したのであきらめる
        delete Distribution;
        delete NeighborList;
    }
    NeighborsList->Add(NeighborList);
    DistributionList->Add(Distribution);
}

//---------------------------------------------------------------------------

