/*
 * Copyright (C) 2014-2019 Muhammad Tayyab Akram
 *
 * 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 <SBConfig.h>

#include "BidiChain.h"
#include "BracketQueue.h"
#include "BracketType.h"
#include "LevelRun.h"
#include "PairingLookup.h"
#include "SBAssert.h"
#include "SBBase.h"
#include "SBLog.h"
#include "IsolatingRun.h"

static void ResolveAvailableBracketPairs(IsolatingRunRef isolatingRun);

static void AttachLevelRunLinks(IsolatingRunRef isolatingRun)
{
    BidiChainRef chain = isolatingRun->bidiChain;
    LevelRunRef baseLevelRun = isolatingRun->baseLevelRun;
    LevelRunRef current;
    LevelRunRef next;

    isolatingRun->_originalLink = BidiChainGetNext(chain, chain->roller);
    BidiChainSetNext(chain, chain->roller, baseLevelRun->firstLink);

    /* Iterate over level runs and attach their links to form an isolating run. */
    for (current = baseLevelRun; (next = current->next); current = next) {
        BidiChainSetNext(chain, current->lastLink, next->firstLink);
    }
    BidiChainSetNext(chain, current->lastLink, chain->roller);

    isolatingRun->_lastLevelRun = current;
    isolatingRun->_sos = RunExtrema_SOR(baseLevelRun->extrema);

    if (!RunKindIsPartialIsolate(baseLevelRun->kind)) {
        isolatingRun->_eos = RunExtrema_EOR(current->extrema);
    } else {
        SBLevel paragraphLevel = isolatingRun->paragraphLevel;
        SBLevel runLevel = baseLevelRun->level;
        SBLevel eosLevel = (runLevel > paragraphLevel ? runLevel : paragraphLevel);
        isolatingRun->_eos = ((eosLevel & 1) ? SBBidiTypeR : SBBidiTypeL);
    }
}

static void AttachOriginalLinks(IsolatingRunRef isolatingRun)
{
    BidiChainRef chain = isolatingRun->bidiChain;
    LevelRunRef current;

    BidiChainSetNext(chain, chain->roller, isolatingRun->_originalLink);

    /* Iterate over level runs and attach original subsequent links. */
    for (current = isolatingRun->baseLevelRun; current; current = current->next) {
        BidiChainSetNext(chain, current->lastLink, current->subsequentLink);
    }
}

static BidiLink ResolveWeakTypes(IsolatingRunRef isolatingRun)
{
    BidiChainRef chain = isolatingRun->bidiChain;
    BidiLink roller = chain->roller;
    BidiLink link;

    BidiLink priorLink;
    SBBidiType sos;

    SBBidiType w1PriorType;
    SBBidiType w2StrongType;
    SBBidiType w4PriorType;
    SBBidiType w5PriorType;
    SBBidiType w7StrongType;

    priorLink = roller;
    sos = isolatingRun->_sos;

    w1PriorType = sos;
    w2StrongType = sos;

    BidiChainForEach(chain, roller, link) {
        SBBidiType type = BidiChainGetType(chain, link);
        SBBoolean forceMerge = SBFalse;

        /* Rule W1 */
        if (type == SBBidiTypeNSM) {
            /* Change the 'type' variable as well because it can be EN on which W2 depends. */
            type = (SBBidiTypeIsIsolate(w1PriorType) ? SBBidiTypeON : w1PriorType);
            BidiChainSetType(chain, link, type);

            /* Fix for 3rd point of rule N0. */
            if (w1PriorType == SBBidiTypeON) {
                forceMerge = SBTrue;
            }
        }
        w1PriorType = type;

        /* Rule W2 */
        if (type == SBBidiTypeEN) {
            if (w2StrongType == SBBidiTypeAL) {
                BidiChainSetType(chain, link, SBBidiTypeAN);
            }
        }
        /*
         * Rule W3
         * NOTE: It is safe to apply W3 in 'else-if' statement because it only depends on type AL.
         *       Even if W2 changes EN to AN, there won't be any harm.
         */
        else if (type == SBBidiTypeAL) {
            BidiChainSetType(chain, link, SBBidiTypeR);
        }

        if (SBBidiTypeIsStrong(type)) {
            /* Save the strong type as it is checked in W2. */
            w2StrongType = type;
        }

        if ((type != SBBidiTypeON && BidiChainGetType(chain, priorLink) == type) || forceMerge) {
            BidiChainAbandonNext(chain, priorLink);
        } else {
            priorLink = link;
        }
    }

    priorLink = roller;
    w4PriorType = sos;
    w5PriorType = sos;
    w7StrongType = sos;

    BidiChainForEach(chain, roller, link) {
        SBBidiType type = BidiChainGetType(chain, link);
        SBBidiType nextType = BidiChainGetType(chain, BidiChainGetNext(chain, link));

        /* Rule W4 */
        if (BidiChainIsSingle(chain, link)
            && SBBidiTypeIsNumberSeparator(type)
            && SBBidiTypeIsNumber(w4PriorType)
            && (w4PriorType == nextType)
            && (w4PriorType == SBBidiTypeEN || type == SBBidiTypeCS))
        {
            /* Change the current type as well because it can be EN on which W5 depends. */
            type = w4PriorType;
            BidiChainSetType(chain, link, type);
        }
        w4PriorType = type;

        /* Rule W5 */
        if (type == SBBidiTypeET && (w5PriorType == SBBidiTypeEN || nextType == SBBidiTypeEN)) {
            /* Change the current type as well because it is EN on which W7 depends. */
            type = SBBidiTypeEN;
            BidiChainSetType(chain, link, type);
        }
        w5PriorType = type;

        switch (type) {
        /* Rule W6 */
        case SBBidiTypeET:
        case SBBidiTypeCS:
        case SBBidiTypeES:
            BidiChainSetType(chain, link, SBBidiTypeON);
            break;

        /*
         * Rule W7
         * NOTE: W7 is expected to be applied after W6. However this is not the case here. The
         *       reason is that W6 can only create the type ON which is not tested in W7 by any
         *       means. So it won't affect the algorithm.
         */
        case SBBidiTypeEN:
            if (w7StrongType == SBBidiTypeL) {
                BidiChainSetType(chain, link, SBBidiTypeL);
            }
            break;

        /*
         * Save the strong type for W7.
         * NOTE: The strong type is expected to be saved after applying W7 because W7 itself creates
         *       a strong type. However the strong type being saved here is based on the type after
         *       W5. This won't effect the algorithm because a single link contains all consecutive
         *       EN types. This means that even if W7 creates a strong type, it will be saved in
         *       next iteration.
         */
        case SBBidiTypeL:
        case SBBidiTypeR:
            w7StrongType = type;
            break;
        }

        if (type != SBBidiTypeON && BidiChainGetType(chain, priorLink) == type) {
            BidiChainAbandonNext(chain, priorLink);
        } else {
            priorLink = link;
        }
    }

    return priorLink;
}

static void ResolveBrackets(IsolatingRunRef isolatingRun)
{
    const SBCodepointSequence *sequence = isolatingRun->codepointSequence;
    SBUInteger paragraphOffset = isolatingRun->paragraphOffset;
    BracketQueueRef queue = &isolatingRun->_bracketQueue;
    BidiChainRef chain = isolatingRun->bidiChain;
    BidiLink roller = chain->roller;
    BidiLink link;

    BidiLink priorStrongLink;
    SBLevel runLevel;

    priorStrongLink = BidiLinkNone;
    runLevel = isolatingRun->baseLevelRun->level;

    BracketQueueReset(queue, SBLevelAsNormalBidiType(runLevel));

    BidiChainForEach(chain, roller, link) {
        SBUInteger stringIndex;
        SBCodepoint codepoint;
        SBBidiType type;

        SBCodepoint bracketValue;
        BracketType bracketType;

        type = BidiChainGetType(chain, link);

        switch (type) {
        case SBBidiTypeON:
            stringIndex = BidiChainGetOffset(chain, link) + paragraphOffset;
            codepoint = SBCodepointSequenceGetCodepointAt(sequence, &stringIndex);
            bracketValue = LookupBracketPair(codepoint, &bracketType);

            switch (bracketType) {
            case BracketTypeOpen:
                if (queue->count < BracketQueueGetMaxCapacity()) {
                    BracketQueueEnqueue(queue, priorStrongLink, link, bracketValue);
                } else {
                    goto Resolve;
                }
                break;

            case BracketTypeClose:
                if (queue->count != 0) {
                    BracketQueueClosePair(queue, link, codepoint);

                    if (BracketQueueShouldDequeue(queue)) {
                        ResolveAvailableBracketPairs(isolatingRun);
                    }
                }
                break;
            }
            break;

        case SBBidiTypeEN:
        case SBBidiTypeAN:
            type = SBBidiTypeR;

        case SBBidiTypeR:
        case SBBidiTypeL:
            if (queue->count != 0) {
                BracketQueueSetStrongType(queue, type);
            }

            priorStrongLink = link;
            break;
        }
    }

Resolve:
    ResolveAvailableBracketPairs(isolatingRun);
}

static void ResolveAvailableBracketPairs(IsolatingRunRef isolatingRun)
{
    BracketQueueRef queue = &isolatingRun->_bracketQueue;
    BidiChainRef chain = isolatingRun->bidiChain;

    SBLevel runLevel;
    SBBidiType embeddingDirection;
    SBBidiType oppositeDirection;

    runLevel = isolatingRun->baseLevelRun->level;
    embeddingDirection = SBLevelAsNormalBidiType(runLevel);
    oppositeDirection = SBLevelAsOppositeBidiType(runLevel);

    while (queue->count != 0) {
        BidiLink openingLink = BracketQueueGetOpeningLink(queue);
        BidiLink closingLink = BracketQueueGetClosingLink(queue);

        if ((openingLink != BidiLinkNone) && (closingLink != BidiLinkNone)) {
            SBBidiType innerStrongType;
            SBBidiType pairType;

            innerStrongType = BracketQueueGetStrongType(queue);

            /* Rule: N0.b */
            if (innerStrongType == embeddingDirection) {
                pairType = innerStrongType;
            }
            /* Rule: N0.c */
            else if (innerStrongType == oppositeDirection) {
                BidiLink priorStrongLink;
                SBBidiType priorStrongType;

                priorStrongLink = BracketQueueGetPriorStrongLink(queue);

                if (priorStrongLink != BidiLinkNone) {
                    BidiLink link;

                    priorStrongType = BidiChainGetType(chain, priorStrongLink);
                    if (SBBidiTypeIsNumber(priorStrongType)) {
                        priorStrongType = SBBidiTypeR;
                    }

                    link = BidiChainGetNext(chain, priorStrongLink);

                    while (link != openingLink) {
                        SBBidiType type = BidiChainGetType(chain, link);
                        if (type == SBBidiTypeL || type == SBBidiTypeR) {
                            priorStrongType = type;
                        }

                        link = BidiChainGetNext(chain, link);
                    }
                } else {
                    priorStrongType = isolatingRun->_sos;
                }

                /* Rule: N0.c.1 */
                if (priorStrongType == oppositeDirection) {
                    pairType = oppositeDirection;
                }
                /* Rule: N0.c.2 */
                else {
                    pairType = embeddingDirection;
                }
            }
            /* Rule: N0.d */
            else {
                pairType = SBBidiTypeNil;
            }

            if (pairType != SBBidiTypeNil) {
                /* Do the substitution */
                BidiChainSetType(chain, openingLink, pairType);
                BidiChainSetType(chain, closingLink, pairType);
            }
        }

        BracketQueueDequeue(queue);
    }
}

static void ResolveNeutrals(IsolatingRunRef isolatingRun)
{
    BidiChainRef chain = isolatingRun->bidiChain;
    BidiLink roller = chain->roller;
    BidiLink link;

    SBLevel runLevel;
    SBBidiType strongType;
    BidiLink neutralLink;

    runLevel = isolatingRun->baseLevelRun->level;
    strongType = isolatingRun->_sos;
    neutralLink = BidiLinkNone;

    BidiChainForEach(chain, roller, link) {
        SBBidiType type = BidiChainGetType(chain, link);
        SBBidiType nextType;

        SBAssert(SBBidiTypeIsStrongOrNumber(type) || SBBidiTypeIsNeutralOrIsolate(type));

        switch (type) {
        case SBBidiTypeL:
            strongType = SBBidiTypeL;
            break;

        case SBBidiTypeR:
        case SBBidiTypeEN:
        case SBBidiTypeAN:
            strongType = SBBidiTypeR;
            break;

        case SBBidiTypeB:                           
        case SBBidiTypeS:
        case SBBidiTypeWS:
        case SBBidiTypeON:
        case SBBidiTypeLRI:
        case SBBidiTypeRLI:                         
        case SBBidiTypeFSI:
        case SBBidiTypePDI:
            if (neutralLink == BidiLinkNone) {
                neutralLink = link;
            }

            nextType = BidiChainGetType(chain, BidiChainGetNext(chain, link));
            if (SBBidiTypeIsNumber(nextType)) {
                nextType = SBBidiTypeR;
            } else if (nextType == SBBidiTypeNil) {
                nextType = isolatingRun->_eos;
            }

            if (SBBidiTypeIsStrong(nextType)) {
                /* Rules N1, N2 */
                SBBidiType resolvedType = (strongType == nextType
                                           ? strongType
                                           : SBLevelAsNormalBidiType(runLevel));

                do {
                    BidiChainSetType(chain, neutralLink, resolvedType);
                    neutralLink = BidiChainGetNext(chain, neutralLink);
                } while (neutralLink != BidiChainGetNext(chain, link));

                neutralLink = BidiLinkNone;
            }
            break;
        }
    }
}

static void ResolveImplicitLevels(IsolatingRunRef isolatingRun)
{
    BidiChainRef chain = isolatingRun->bidiChain;
    BidiLink roller = chain->roller;
    BidiLink link;

    SBLevel runLevel = isolatingRun->baseLevelRun->level;
    
    if ((runLevel & 1) == 0) {
        BidiChainForEach(chain, roller, link) {
            SBBidiType type = BidiChainGetType(chain, link);
            SBLevel level = BidiChainGetLevel(chain, link);
            
            SBAssert(SBBidiTypeIsStrongOrNumber(type));
            
            /* Rule I1 */
            if (type == SBBidiTypeR) {
                BidiChainSetLevel(chain, link, level + 1);
            } else if (type != SBBidiTypeL) {
                BidiChainSetLevel(chain, link, level + 2);
            }
        }
    } else {
        BidiChainForEach(chain, roller, link) {
            SBBidiType type = BidiChainGetType(chain, link);
            SBLevel level = BidiChainGetLevel(chain, link);
            
            SBAssert(SBBidiTypeIsStrongOrNumber(type));
            
            /* Rule I2 */
            if (type != SBBidiTypeR) {
                BidiChainSetLevel(chain, link, level + 1);
            }
        }
    }
}

SB_INTERNAL void IsolatingRunInitialize(IsolatingRunRef isolatingRun)
{
    BracketQueueInitialize(&isolatingRun->_bracketQueue);
}

SB_INTERNAL void IsolatingRunResolve(IsolatingRunRef isolatingRun)
{
    BidiLink lastLink;
    BidiLink subsequentLink;

    SB_LOG_BLOCK_OPENER("Identified Isolating Run");

    /* Attach level run links to form isolating run. */
    AttachLevelRunLinks(isolatingRun);
    /* Save last subsequent link. */
    subsequentLink = isolatingRun->_lastLevelRun->subsequentLink;

    SB_LOG_STATEMENT("Range", 1, SB_LOG_RUN_RANGE(isolatingRun));
    SB_LOG_STATEMENT("Types", 1, SB_LOG_RUN_TYPES(isolatingRun));
    SB_LOG_STATEMENT("Level", 1, SB_LOG_LEVEL(isolatingRun->baseLevelRun->level));
    SB_LOG_STATEMENT("SOS", 1, SB_LOG_BIDI_TYPE(isolatingRun->_sos));
    SB_LOG_STATEMENT("EOS", 1, SB_LOG_BIDI_TYPE(isolatingRun->_eos));

    /* Rules W1-W7 */
    lastLink = ResolveWeakTypes(isolatingRun);
    SB_LOG_BLOCK_OPENER("Resolved Weak Types");
    SB_LOG_STATEMENT("Types", 1, SB_LOG_RUN_TYPES(isolatingRun));
    SB_LOG_BLOCK_CLOSER();

    /* Rule N0 */
    ResolveBrackets(isolatingRun);
    SB_LOG_BLOCK_OPENER("Resolved Brackets");
    SB_LOG_STATEMENT("Types", 1, SB_LOG_RUN_TYPES(isolatingRun));
    SB_LOG_BLOCK_CLOSER();

    /* Rules N1, N2 */
    ResolveNeutrals(isolatingRun);
    SB_LOG_BLOCK_OPENER("Resolved Neutrals");
    SB_LOG_STATEMENT("Types", 1, SB_LOG_RUN_TYPES(isolatingRun));
    SB_LOG_BLOCK_CLOSER();

    /* Rules I1, I2 */
    ResolveImplicitLevels(isolatingRun);
    SB_LOG_BLOCK_OPENER("Resolved Implicit Levels");
    SB_LOG_STATEMENT("Levels", 1, SB_LOG_RUN_LEVELS(isolatingRun));
    SB_LOG_BLOCK_CLOSER();

    /* Re-attach original links. */
    AttachOriginalLinks(isolatingRun);
    /* Attach new final link (of isolating run) with last subsequent link. */
    BidiChainSetNext(isolatingRun->bidiChain, lastLink, subsequentLink);

    SB_LOG_BLOCK_CLOSER();
}

SB_INTERNAL void IsolatingRunFinalize(IsolatingRunRef isolatingRun)
{
    BracketQueueFinalize(&isolatingRun->_bracketQueue);
}
