/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.h"

#include "dictionary/interface/dictionary_header_structure_policy.h"
#include "dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
#include "dictionary/structure/pt_common/pt_node_params.h"
#include "dictionary/structure/pt_common/pt_node_writer.h"

namespace latinime {

bool DynamicPtGcEventListeners
        ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
                ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
    // PtNode is useless when the PtNode is not a terminal and doesn't have any not useless
    // children.
    bool isUselessPtNode = !ptNodeParams->isTerminal();
    if (ptNodeParams->isTerminal() && !ptNodeParams->representsNonWordInfo()) {
        bool needsToKeepPtNode = true;
        if (!mPtNodeWriter->updatePtNodeProbabilityAndGetNeedsToKeepPtNodeAfterGC(
                ptNodeParams, &needsToKeepPtNode)) {
            AKLOGE("Cannot update PtNode probability or get needs to keep PtNode after GC.");
            return false;
        }
        if (!needsToKeepPtNode) {
            isUselessPtNode = true;
        }
    }
    if (mChildrenValue > 0) {
        isUselessPtNode = false;
    } else if (ptNodeParams->isTerminal()) {
        // Remove children as all children are useless.
        if (!mPtNodeWriter->updateChildrenPosition(ptNodeParams,
                NOT_A_DICT_POS /* newChildrenPosition */)) {
            return false;
        }
    }
    if (isUselessPtNode) {
        // Current PtNode is no longer needed. Mark it as deleted.
        if (!mPtNodeWriter->markPtNodeAsDeleted(ptNodeParams)) {
            return false;
        }
    } else {
        mValueStack.back() += 1;
        if (ptNodeParams->isTerminal() && !ptNodeParams->representsNonWordInfo()) {
            mValidUnigramCount += 1;
        }
    }
    return true;
}

bool DynamicPtGcEventListeners::TraversePolicyToUpdateBigramProbability
        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
    if (!ptNodeParams->isDeleted()) {
        int bigramEntryCount = 0;
        if (!mPtNodeWriter->updateAllBigramEntriesAndDeleteUselessEntries(ptNodeParams,
                &bigramEntryCount)) {
            return false;
        }
        mValidBigramEntryCount += bigramEntryCount;
    }
    return true;
}

// Writes dummy PtNode array size when the head of PtNode array is read.
bool DynamicPtGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
        ::onDescend(const int ptNodeArrayPos) {
    mValidPtNodeCount = 0;
    int writingPos = mBufferToWrite->getTailPosition();
    mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.insert(
            PtNodeWriter::PtNodeArrayPositionRelocationMap::value_type(ptNodeArrayPos, writingPos));
    // Writes dummy PtNode array size because arrays can have a forward link or needles PtNodes.
    // This field will be updated later in onReadingPtNodeArrayTail() with actual PtNode count.
    mPtNodeArraySizeFieldPos = writingPos;
    return DynamicPtWritingUtils::writePtNodeArraySizeAndAdvancePosition(
            mBufferToWrite, 0 /* arraySize */, &writingPos);
}

// Write PtNode array terminal and actual PtNode array size.
bool DynamicPtGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
        ::onReadingPtNodeArrayTail() {
    int writingPos = mBufferToWrite->getTailPosition();
    // Write PtNode array terminal.
    if (!DynamicPtWritingUtils::writeForwardLinkPositionAndAdvancePosition(
            mBufferToWrite, NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
        return false;
    }
    // Write actual PtNode array size.
    if (!DynamicPtWritingUtils::writePtNodeArraySizeAndAdvancePosition(
            mBufferToWrite, mValidPtNodeCount, &mPtNodeArraySizeFieldPos)) {
        return false;
    }
    return true;
}

// Write valid PtNode to buffer and memorize mapping from the old position to the new position.
bool DynamicPtGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
    if (ptNodeParams->isDeleted()) {
        // Current PtNode is not written in new buffer because it has been deleted.
        mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
                PtNodeWriter::PtNodePositionRelocationMap::value_type(
                        ptNodeParams->getHeadPos(), NOT_A_DICT_POS));
        return true;
    }
    int writingPos = mBufferToWrite->getTailPosition();
    mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
            PtNodeWriter::PtNodePositionRelocationMap::value_type(
                    ptNodeParams->getHeadPos(), writingPos));
    mValidPtNodeCount++;
    // Writes current PtNode.
    return mPtNodeWriter->writePtNodeAndAdvancePosition(ptNodeParams, &writingPos);
}

bool DynamicPtGcEventListeners::TraversePolicyToUpdateAllPositionFields
        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
    // Updates parent position.
    int bigramCount = 0;
    if (!mPtNodeWriter->updateAllPositionFields(ptNodeParams, mDictPositionRelocationMap,
            &bigramCount)) {
        return false;
    }
    mBigramCount += bigramCount;
    if (ptNodeParams->isTerminal()) {
        mUnigramCount++;
    }
    return true;
}

} // namespace latinime
