/*
 * Copyright (C) 2014 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 "offdevice_intermediate_dict/offdevice_intermediate_dict.h"

#include "offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node.h"

namespace latinime {
namespace dicttoolkit {

bool OffdeviceIntermediateDict::addWord(const WordProperty &wordProperty) {
    const CodePointArrayView codePoints = wordProperty.getCodePoints();
    if (codePoints.empty() || codePoints.size() > MAX_WORD_LENGTH) {
        return false;
    }
    return addWordInner(codePoints, wordProperty, mRootPtNodeArray);
}

bool OffdeviceIntermediateDict::addWordInner(const CodePointArrayView codePoints,
        const WordProperty &wordProperty, OffdeviceIntermediateDictPtNodeArray &ptNodeArray) {
    auto ptNodeList = ptNodeArray.getMutablePtNodeList();
    auto ptNodeIt = ptNodeList->begin();
    for (; ptNodeIt != ptNodeList->end(); ++ptNodeIt) {
        const auto &ptNode = *ptNodeIt;
        const CodePointArrayView ptNodeCodePoints = ptNode->getPtNodeCodePoints();
        if (codePoints[0] < ptNodeCodePoints[0]) {
            continue;
        }
        if (codePoints[0] > ptNodeCodePoints[0]) {
            break;
        }
        size_t i = 1;
        for (; i < codePoints.size(); ++i) {
            if (i >= ptNodeCodePoints.size()) {
                // Add new child.
                return addWordInner(codePoints.skip(i), wordProperty,
                        ptNode->getChildrenPtNodeArray());
            }
            if (codePoints[i] != ptNodeCodePoints[i]) {
                break;
            }
        }
        if (codePoints.size() == i && codePoints.size() == ptNodeCodePoints.size()) {
            // All code points matched.
            if (ptNode->getWordProperty()) {
                //  Adding the same word multiple times is not supported.
                return false;
            }
            ptNodeList->insert(ptNodeIt,
                    std::make_shared<OffdeviceIntermediateDictPtNode>(wordProperty, *ptNode));
            ptNodeList->erase(ptNodeIt);
            return true;
        }
        // The (i+1)-th elements are different.
        // Create and Add new parent ptNode for the common part.
        auto newPtNode = codePoints.size() == i
                ? std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints, wordProperty)
                : std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints.limit(i));
        ptNodeList->insert(ptNodeIt, newPtNode);
        OffdeviceIntermediateDictPtNodeArray &childrenPtNodeArray =
                newPtNode->getChildrenPtNodeArray();
        // Add new child for the existing ptNode.
        childrenPtNodeArray.getMutablePtNodeList()->push_back(
                std::make_shared<OffdeviceIntermediateDictPtNode>(
                        ptNodeCodePoints.skip(i), *ptNode));
        ptNodeList->erase(ptNodeIt);
        if (codePoints.size() != i) {
            // Add a child for the new word.
            return addWordInner(codePoints.skip(i), wordProperty, childrenPtNodeArray);
        }
        return true;
    }
    ptNodeList->insert(ptNodeIt,
            std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints, wordProperty));
    return true;
}

const WordProperty *OffdeviceIntermediateDict::getWordProperty(
        const CodePointArrayView codePoints) const {
    const OffdeviceIntermediateDictPtNodeArray *ptNodeArray = &mRootPtNodeArray;
    for (size_t i = 0; i < codePoints.size();) {
        bool foundNext = false;
        for (const auto& ptNode : ptNodeArray->getPtNodeList()) {
            const CodePointArrayView ptNodeCodePoints = ptNode->getPtNodeCodePoints();
            if (codePoints[i] < ptNodeCodePoints[0]) {
                continue;
            }
            if (codePoints[i] > ptNodeCodePoints[0]
                     || codePoints.size() < ptNodeCodePoints.size()) {
                return nullptr;
            }
            for (size_t j = 1; j < ptNodeCodePoints.size(); ++j) {
                if (codePoints[i + j] != ptNodeCodePoints[j]) {
                    return nullptr;
                }
            }
            i += ptNodeCodePoints.size();
            if (i == codePoints.size()) {
                return ptNode->getWordProperty();
            }
            ptNodeArray = &ptNode->getChildrenPtNodeArray();
            foundNext = true;
            break;
        }
        if (!foundNext) {
            break;
        }
    }
    return nullptr;
}

} // namespace dicttoolkit
} // namespace latinime