/*
 * 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 <stddef.h>
#include <stdlib.h>

#include "BidiChain.h"
#include "SBAssert.h"
#include "SBBase.h"
#include "BracketQueue.h"

static void BracketQueueFinalizePairs(BracketQueueRef queue, BracketQueueListRef list, SBInteger top)
{
    do {
        SBInteger limit = (list == queue->_rearList ? queue->_rearTop : BracketQueueList_MaxIndex);

        while (++top <= limit) {
            if (list->openingLink[top] != BidiLinkNone
                && list->closingLink[top] == BidiLinkNone) {
                list->openingLink[top] = BidiLinkNone;
            }
        }

        list = list->next;
        top = 0;
    } while (list);
}

SB_INTERNAL void BracketQueueInitialize(BracketQueueRef queue)
{
    queue->_firstList.previous = NULL;
    queue->_firstList.next = NULL;
    queue->_frontList = NULL;
    queue->_rearList = NULL;
    queue->count = 0;
    queue->shouldDequeue = SBFalse;
}

SB_INTERNAL void BracketQueueReset(BracketQueueRef queue, SBBidiType direction)
{
    queue->_frontList = &queue->_firstList;
    queue->_rearList = &queue->_firstList;
    queue->_frontTop = 0;
    queue->_rearTop = -1;
    queue->count = 0;
    queue->shouldDequeue = SBFalse;
    queue->_direction = direction;
}

SB_INTERNAL void BracketQueueEnqueue(BracketQueueRef queue,
   BidiLink priorStrongLink, BidiLink openingLink, SBCodepoint bracket)
{
    BracketQueueListRef list;
    SBInteger top;

    /* The queue can only take a maximum of 63 elements. */
    SBAssert(queue->count < BracketQueueGetMaxCapacity());

    if (queue->_rearTop != BracketQueueList_MaxIndex) {
        list = queue->_rearList;
        top = ++queue->_rearTop;
    } else {
        BracketQueueListRef rearList;

        rearList = queue->_rearList;
        list = rearList->next;

        if (!list) {
            list = malloc(sizeof(BracketQueueList));
            list->previous = rearList;
            list->next = NULL;

            rearList->next = list;
        }

        queue->_rearList = list;
        queue->_rearTop = top = 0;
    }
    queue->count += 1;

    list->priorStrongLink[top] = priorStrongLink;
    list->openingLink[top] = openingLink;
    list->closingLink[top] = BidiLinkNone;
    list->bracket[top] = bracket;
    list->strongType[top] = SBBidiTypeNil;
}

SB_INTERNAL void BracketQueueDequeue(BracketQueueRef queue)
{
    /* The queue must NOT be empty. */
    SBAssert(queue->count != 0);

    if (queue->_frontTop != BracketQueueList_MaxIndex) {
        queue->_frontTop += 1;
    } else {
        BracketQueueListRef frontList = queue->_frontList;

        if (frontList == queue->_rearList) {
            queue->_rearTop = -1;
        } else {
            queue->_frontList = frontList->next;
        }

        queue->_frontTop = 0;
    }

    queue->count -= 1;
}

SB_INTERNAL void BracketQueueSetStrongType(BracketQueueRef queue, SBBidiType strongType)
{
    BracketQueueListRef list = queue->_rearList;
    SBInteger top = queue->_rearTop;

    while (1) {
        SBInteger limit = (list == queue->_frontList ? queue->_frontTop : 0);

        do {
            if (list->closingLink[top] == BidiLinkNone
                && list->strongType[top] != queue->_direction) {
                list->strongType[top] = strongType;
            }
        } while (top-- > limit);

        if (list == queue->_frontList) {
            break;
        }

        list = list->previous;
        top = BracketQueueList_MaxIndex;
    }
}

SB_INTERNAL void BracketQueueClosePair(BracketQueueRef queue, BidiLink closingLink, SBCodepoint bracket)
{
    BracketQueueListRef list = queue->_rearList;
    SBInteger top = queue->_rearTop;
    SBCodepoint canonical;

    switch (bracket) {
    case 0x232A:
        canonical = 0x3009;
        break;

    case 0x3009:
        canonical = 0x232A;
        break;

    default:
        canonical = bracket;
        break;
    }

    while (1) {
        SBBoolean isFrontList = (list == queue->_frontList);
        SBInteger limit = (isFrontList ? queue->_frontTop : 0);

        do {
            if (list->openingLink[top] != BidiLinkNone
                && list->closingLink[top] == BidiLinkNone
                && (list->bracket[top] == bracket || list->bracket[top] == canonical)) {
                list->closingLink[top] = closingLink;
                BracketQueueFinalizePairs(queue, list, top);

                if (isFrontList && top == queue->_frontTop) {
                    queue->shouldDequeue = SBTrue;
                }

                return;
            }
        } while (top-- > limit);

        if (isFrontList) {
            break;
        }

        list = list->previous;
        top = BracketQueueList_MaxIndex;
    }
}

SB_INTERNAL SBBoolean BracketQueueShouldDequeue(BracketQueueRef queue)
{
    return queue->shouldDequeue;
}

SB_INTERNAL BidiLink BracketQueueGetPriorStrongLink(BracketQueueRef queue)
{
    return queue->_frontList->priorStrongLink[queue->_frontTop];
}

SB_INTERNAL BidiLink BracketQueueGetOpeningLink(BracketQueueRef queue)
{
    return queue->_frontList->openingLink[queue->_frontTop];
}

SB_INTERNAL BidiLink BracketQueueGetClosingLink(BracketQueueRef queue)
{
    return queue->_frontList->closingLink[queue->_frontTop];
}

SB_INTERNAL SBBidiType BracketQueueGetStrongType(BracketQueueRef queue)
{
    return queue->_frontList->strongType[queue->_frontTop];
}

SB_INTERNAL void BracketQueueFinalize(BracketQueueRef queue)
{
    BracketQueueListRef list = queue->_firstList.next;

    while (list) {
        BracketQueueListRef next = list->next;
        free(list);
        list = next;
    }
}
