/**
 * Copyright (c) 2015 QFish <im@qfi.sh>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "selector.h"
#include <string.h>
#include <assert.h>

#undef	assert
#define assert(x)

// Refs:
// http://www.w3.org/TR/css3-selectors/
//

static KatanaPseudoType name_to_pseudo_type(const char* name, bool hasArguments);

bool katana_selector_crosses_tree_scopes(const KatanaSelector* selector)
{
    // TODO: To be supported
    return false;
}

// bool katana_is_attribute_selector(const KatanaSelector* selector)
// {
//     return selector->match == KatanaSelectorMatchAttributeExact
//     || selector->match == KatanaSelectorMatchAttributeSet
//     || selector->match == KatanaSelectorMatchAttributeList
//     || selector->match == KatanaSelectorMatchAttributeHyphen
//     || selector->match == KatanaSelectorMatchAttributeContain
//     || selector->match == KatanaSelectorMatchAttributeBegin
//     || selector->match == KatanaSelectorMatchAttributeEnd;
// }

KatanaPseudoType katana_parse_pseudo_type(const char* name, bool hasArguments)
{
    KatanaPseudoType pseudoType = name_to_pseudo_type(name, hasArguments);
    if (pseudoType != KatanaPseudoUnknown)
        return pseudoType;
    
    if (katana_string_has_prefix(name, "-webkit-"))
        return KatanaPseudoWebKitCustomElement;
    
    return KatanaPseudoUnknown;
}

void katana_selector_extract_pseudo_type(KatanaSelector* selector)
{
    if (selector->pseudo == KatanaPseudoNotParsed)
        selector->pseudo = KatanaPseudoUnknown;
    
    if (selector->match != KatanaSelectorMatchPseudoClass && selector->match != KatanaSelectorMatchPseudoElement && selector->match != KatanaSelectorMatchPagePseudoClass)
        return;
    bool hasArguments = (NULL != selector->data->argument) || (NULL != selector->data->selectors);    
    selector->pseudo = katana_parse_pseudo_type(selector->data->value, hasArguments);
    
    bool element = false; // pseudo-element
    bool compat = false; // single colon compatbility mode
    bool isPagePseudoClass = false; // Page pseudo-class
    
    switch (selector->pseudo) {
        case KatanaPseudoAfter:
        case KatanaPseudoBefore:
        case KatanaPseudoFirstLetter:
        case KatanaPseudoFirstLine:
            compat = true;
        case KatanaPseudoBackdrop:
        case KatanaPseudoCue:
        case KatanaPseudoResizer:
        case KatanaPseudoScrollbar:
        case KatanaPseudoScrollbarCorner:
        case KatanaPseudoScrollbarButton:
        case KatanaPseudoScrollbarThumb:
        case KatanaPseudoScrollbarTrack:
        case KatanaPseudoScrollbarTrackPiece:
        case KatanaPseudoSelection:
        case KatanaPseudoWebKitCustomElement:
        case KatanaPseudoContent:
        case KatanaPseudoShadow:
            element = true;
            break;
        case KatanaPseudoUnknown:
        case KatanaPseudoEmpty:
        case KatanaPseudoFirstChild:
        case KatanaPseudoFirstOfType:
        case KatanaPseudoLastChild:
        case KatanaPseudoLastOfType:
        case KatanaPseudoOnlyChild:
        case KatanaPseudoOnlyOfType:
        case KatanaPseudoNthChild:
        case KatanaPseudoNthOfType:
        case KatanaPseudoNthLastChild:
        case KatanaPseudoNthLastOfType:
        case KatanaPseudoLink:
        case KatanaPseudoVisited:
        case KatanaPseudoAny:
        case KatanaPseudoAnyLink:
        case KatanaPseudoAutofill:
        case KatanaPseudoHover:
        case KatanaPseudoDrag:
        case KatanaPseudoFocus:
        case KatanaPseudoActive:
        case KatanaPseudoChecked:
        case KatanaPseudoEnabled:
        case KatanaPseudoFullPageMedia:
        case KatanaPseudoDefault:
        case KatanaPseudoDisabled:
        case KatanaPseudoOptional:
        case KatanaPseudoRequired:
        case KatanaPseudoReadOnly:
        case KatanaPseudoReadWrite:
        case KatanaPseudoScope:
        case KatanaPseudoValid:
        case KatanaPseudoInvalid:
        case KatanaPseudoIndeterminate:
        case KatanaPseudoTarget:
        case KatanaPseudoLang:
        case KatanaPseudoNot:
        case KatanaPseudoRoot:
        case KatanaPseudoWindowInactive:
        case KatanaPseudoCornerPresent:
        case KatanaPseudoDecrement:
        case KatanaPseudoIncrement:
        case KatanaPseudoHorizontal:
        case KatanaPseudoVertical:
        case KatanaPseudoStart:
        case KatanaPseudoEnd:
        case KatanaPseudoDoubleButton:
        case KatanaPseudoSingleButton:
        case KatanaPseudoNoButton:
        case KatanaPseudoNotParsed:
        case KatanaPseudoFullScreen:
        case KatanaPseudoFullScreenDocument:
        case KatanaPseudoFullScreenAncestor:
        case KatanaPseudoInRange:
        case KatanaPseudoOutOfRange:
        case KatanaPseudoFutureCue:
        case KatanaPseudoPastCue:
        case KatanaPseudoHost:
        case KatanaPseudoHostContext:
        case KatanaPseudoUnresolved:
        case KatanaPseudoSpatialNavigationFocus:
        case KatanaPseudoListBox:
            break;
        case KatanaPseudoFirstPage:
        case KatanaPseudoLeftPage:
        case KatanaPseudoRightPage:
            isPagePseudoClass = true;
            break;
    }
    
    bool matchPagePseudoClass = (selector->match == KatanaSelectorMatchPagePseudoClass);
    if (matchPagePseudoClass != isPagePseudoClass)
        selector->pseudo = KatanaPseudoUnknown;
    else if (selector->match == KatanaSelectorMatchPseudoClass && element) {
        if (!compat)
            selector->pseudo = KatanaPseudoUnknown;
        else
            selector->match = KatanaSelectorMatchPseudoElement;
    } else if (selector->match == KatanaSelectorMatchPseudoElement && !element)
        selector->pseudo = KatanaPseudoUnknown;
}

bool katana_selector_matches_pseudo_element(KatanaSelector* selector)
{
    if (selector->pseudo == KatanaPseudoUnknown)
        katana_selector_extract_pseudo_type(selector);
    return selector->match == KatanaSelectorMatchPseudoElement;
}

bool katana_selector_is_custom_pseudo_element(KatanaSelector* selector)
{
    return selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoWebKitCustomElement;
}

bool katana_selector_is_direct_adjacent(KatanaSelector* selector)
{
    return selector->relation == KatanaSelectorRelationDirectAdjacent || selector->relation == KatanaSelectorRelationIndirectAdjacent;
}

bool katana_selector_is_adjacent(KatanaSelector* selector)
{
    return selector->relation == KatanaSelectorRelationDirectAdjacent;
}

bool katana_selector_is_shadow(KatanaSelector* selector)
{
    return selector->relation == KatanaSelectorRelationShadowPseudo || selector->relation == KatanaSelectorRelationShadowDeep;
}

bool katana_selector_is_sibling(KatanaSelector* selector)
{
    katana_selector_extract_pseudo_type(selector);

    KatanaPseudoType type = selector->pseudo;
    return selector->relation == KatanaSelectorRelationDirectAdjacent
        || selector->relation == KatanaSelectorRelationIndirectAdjacent
        || type == KatanaPseudoEmpty
        || type == KatanaPseudoFirstChild
        || type == KatanaPseudoFirstOfType
        || type == KatanaPseudoLastChild
        || type == KatanaPseudoLastOfType
        || type == KatanaPseudoOnlyChild
        || type == KatanaPseudoOnlyOfType
        || type == KatanaPseudoNthChild
        || type == KatanaPseudoNthOfType
        || type == KatanaPseudoNthLastChild
        || type == KatanaPseudoNthLastOfType;
}

bool katana_selector_is_attribute(const KatanaSelector* selector)
{
    return selector->match >= KatanaSelectorMatchFirstAttribute;
}

bool katana_selector_is_content_pseudo_element(KatanaSelector* selector)
{
    katana_selector_extract_pseudo_type(selector);
    return selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoContent;
}

bool katana_selector_is_shadow_pseudo_element(KatanaSelector* selector)
{
    return selector->match == KatanaSelectorMatchPseudoElement
            && selector->pseudo == KatanaPseudoShadow;
}

bool katana_selector_is_host_pseudo_class(KatanaSelector* selector)
{
    return selector->match == KatanaSelectorMatchPseudoClass && (selector->pseudo == KatanaPseudoHost || selector->pseudo == KatanaPseudoHostContext);
}

bool katana_selector_is_tree_boundary_crossing(KatanaSelector* selector)
{
    katana_selector_extract_pseudo_type(selector);
    return selector->match == KatanaSelectorMatchPseudoClass && (selector->pseudo == KatanaPseudoHost || selector->pseudo == KatanaPseudoHostContext);
}

bool katana_selector_is_insertion_point_crossing(KatanaSelector* selector)
{
    katana_selector_extract_pseudo_type(selector);
    return (selector->match == KatanaSelectorMatchPseudoClass && selector->pseudo == KatanaPseudoHostContext)
        || (selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoContent);
}

KatanaParserString* katana_build_relation_selector_string(KatanaParser* parser, const char* relation, KatanaParserString* string, KatanaParserString* next, KatanaSelector* tagHistory)
{
    if ( NULL != relation ) {
        katana_string_prepend_characters(parser, relation, string);
    }
    
    if ( NULL != next ) {
        katana_string_append_string(parser, next, string);
    }
    
    KatanaParserString * result = katana_selector_to_string(parser, tagHistory, (KatanaParserString*)string);
    katana_parser_deallocate(parser, (void*) string->data);
    katana_parser_deallocate(parser, (void*) string);
    return result;
}

KatanaParserString* katana_selector_to_string(KatanaParser* parser, KatanaSelector* selector, KatanaParserString* next)
{
    KatanaParserString* string = katana_parser_allocate(parser, sizeof(KatanaParserString));
    katana_string_init(parser, string);
    
    bool tag_is_implicit = true;
    
    if (selector->match == KatanaSelectorMatchTag && tag_is_implicit)
    {
        if ( NULL == selector->tag->prefix )
            katana_string_append_characters(parser, selector->tag->local, string);
        else {
            katana_string_append_characters(parser, selector->tag->prefix, string);
            katana_string_append_characters(parser, "|", string);
            katana_string_append_characters(parser, selector->tag->local, string);
        }
    }

    const KatanaSelector* cs = selector;

    while (true) {
        if (cs->match == KatanaSelectorMatchId) {
            katana_string_append_characters(parser, "#", string);
            katana_string_append_characters(parser, cs->data->value, string);
        } else if (cs->match == KatanaSelectorMatchClass) {
            katana_string_append_characters(parser, ".", string);
            katana_string_append_characters(parser, cs->data->value, string);
        } else if (cs->match == KatanaSelectorMatchPseudoClass || cs->match == KatanaSelectorMatchPagePseudoClass) {
            katana_string_append_characters(parser, ":", string);
            katana_string_append_characters(parser, cs->data->value, string);
            
            switch (cs->pseudo) {
                case KatanaPseudoAny:
                case KatanaPseudoNot:
                case KatanaPseudoHost:
                case KatanaPseudoHostContext: {
                    if ( cs->data->selectors ) {
                        KatanaArray* sels = cs->data->selectors;
                        for (size_t i=0; i<sels->length; i++) {
                            KatanaParserString* str = katana_selector_to_string(parser, sels->data[i], NULL);
                            katana_string_append_string(parser, str, string);
                            katana_parser_deallocate(parser, (void*) str->data);
                            katana_parser_deallocate(parser, (void*) str);
                            if ( i != sels->length -1 ) {
                                katana_string_append_characters(parser, ", ", string);
                            }
                        }
                        katana_string_append_characters(parser, ")", string);
                    }
                }
                    break;
                case KatanaPseudoLang:
                case KatanaPseudoNthChild:
                case KatanaPseudoNthLastChild:
                case KatanaPseudoNthOfType:
                case KatanaPseudoNthLastOfType: {
                    katana_string_append_characters(parser, cs->data->argument, string);
                    katana_string_append_characters(parser, ")", string);
                }
                    break;
                default:
                    break;
            }
        } else if (cs->match == KatanaSelectorMatchPseudoElement) {
            katana_string_append_characters(parser, "::", string);
            katana_string_append_characters(parser, cs->data->value, string);
        } else if (katana_selector_is_attribute(cs)) {
            katana_string_append_characters(parser, "[", string);
            if (NULL != cs->data->attribute->prefix) {
                katana_string_append_characters(parser, cs->data->attribute->prefix, string);
                katana_string_append_characters(parser, "|", string);
            }
            katana_string_append_characters(parser, cs->data->attribute->local, string);
            switch (cs->match) {
                case KatanaSelectorMatchAttributeExact:
                    katana_string_append_characters(parser, "=", string);
                    break;
                case KatanaSelectorMatchAttributeSet:
                    katana_string_append_characters(parser, "]", string);
                    break;
                case KatanaSelectorMatchAttributeList:
                    katana_string_append_characters(parser, "~=", string);
                    break;
                case KatanaSelectorMatchAttributeHyphen:
                    katana_string_append_characters(parser, "|=", string);
                    break;
                case KatanaSelectorMatchAttributeBegin:
                    katana_string_append_characters(parser, "^=", string);
                    break;
                case KatanaSelectorMatchAttributeEnd:
                    katana_string_append_characters(parser, "$=", string);
                    break;
                case KatanaSelectorMatchAttributeContain:
                    katana_string_append_characters(parser, "*=", string);
                    break;
                default:
                    break;
            }
            if (cs->match != KatanaSelectorMatchAttributeSet) {
                katana_string_append_characters(parser, "\"", string);
                katana_string_append_characters(parser, cs->data->value, string);
                katana_string_append_characters(parser, "\"", string);
                if (cs->data->bits.attributeMatchType == KatanaAttributeMatchTypeCaseInsensitive)
                    katana_string_append_characters(parser, " i", string);
                katana_string_append_characters(parser, "]", string);
            }
        }
        if (cs->relation != KatanaSelectorRelationSubSelector || !cs->tagHistory)
            break;
        cs = cs->tagHistory;
    }

    KatanaSelector* tagHistory = cs->tagHistory;

    if ( NULL != tagHistory ) {
        switch (cs->relation) {
            case KatanaSelectorRelationDescendant:
            {
                return katana_build_relation_selector_string(parser, " ", string, next, tagHistory);
            }
            case KatanaSelectorRelationChild:
            {
                return katana_build_relation_selector_string(parser, " > ", string, next, tagHistory);
            }
            case KatanaSelectorRelationShadowDeep:
            {
                return katana_build_relation_selector_string(parser, " /deep/ ", string, next, tagHistory);
            }
            case KatanaSelectorRelationDirectAdjacent:
            {
                return katana_build_relation_selector_string(parser, " + ", string, next, tagHistory);
            }
            case KatanaSelectorRelationIndirectAdjacent:
            {
                return katana_build_relation_selector_string(parser, " ~ ", string, next, tagHistory);
            }
            case KatanaSelectorRelationSubSelector:
            {
                return NULL;
            }
            case KatanaSelectorRelationShadowPseudo:
            {
                return katana_build_relation_selector_string(parser, NULL, string, next, tagHistory);
            }
        }
    }
    
    if ( NULL != next ) {
        katana_string_append_string(parser, (KatanaParserString*)next, string);
    }
    
    return (KatanaParserString*)string;
}

unsigned calc_specificity_for_one_selector(const KatanaSelector* selector)
{
    switch ( selector->match ) {
        case KatanaSelectorMatchId:
            return 0x10000;
            
        case KatanaSelectorMatchPseudoClass:
        case KatanaSelectorMatchAttributeExact:
        case KatanaSelectorMatchClass:
        case KatanaSelectorMatchAttributeSet:
        case KatanaSelectorMatchAttributeList:
        case KatanaSelectorMatchAttributeHyphen:
        case KatanaSelectorMatchPseudoElement:
        case KatanaSelectorMatchAttributeContain:
        case KatanaSelectorMatchAttributeBegin:
        case KatanaSelectorMatchAttributeEnd:
            return 0x100;
            
        case KatanaSelectorMatchTag:
            return !stricmp(selector->tag->local, "*") ? 0 : 1;
        case KatanaSelectorMatchUnknown:
        case KatanaSelectorMatchPagePseudoClass:
            return 0;
    }
    
    return 0;
}

unsigned katana_calc_specificity_for_selector(KatanaSelector* selector)
{
    if ( NULL == selector ) {
        return 0;
    }
    
    static const unsigned idMask = 0xff0000;
    static const unsigned classMask = 0xff00;
    static const unsigned elementMask = 0xff;
    
    unsigned total = 0;
    unsigned temp = 0;
    
    for (const KatanaSelector * next = selector; next; next = next->tagHistory)
    {
        temp = total + calc_specificity_for_one_selector(next);

        if ((temp & idMask) < (total & idMask))
            total |= idMask;
        else if ((temp & classMask) < (total & classMask))
            total |= classMask;
        else if ((temp & elementMask) < (total & elementMask))
            total |= elementMask;
        else
            total = temp;
    }
        
    return total;
}

// Could be made smaller and faster by replacing pointer with an
// offset into a string buffer and making the bit fields smaller but
// that could not be maintained by hand.
typedef struct {
    const char* string;
    unsigned type:8;
} KatanaNameToPseudoStruct;

// These tables should be kept sorted.
const static KatanaNameToPseudoStruct kPseudoTypeWithoutArgumentsMap[] = {
    {"-internal-list-box",            KatanaPseudoListBox},
    {"-internal-media-controls-cast-button", KatanaPseudoWebKitCustomElement},
    {"-internal-media-controls-overlay-cast-button", KatanaPseudoWebKitCustomElement},
    {"-internal-spatial-navigation-focus", KatanaPseudoSpatialNavigationFocus},
    {"-webkit-any-link",              KatanaPseudoAnyLink},
    {"-webkit-autofill",              KatanaPseudoAutofill},
    {"-webkit-drag",                  KatanaPseudoDrag},
    {"-webkit-full-page-media",       KatanaPseudoFullPageMedia},
    {"-webkit-full-screen",           KatanaPseudoFullScreen},
    {"-webkit-full-screen-ancestor",  KatanaPseudoFullScreenAncestor},
    {"-webkit-full-screen-document",  KatanaPseudoFullScreenDocument},
    {"-webkit-resizer",               KatanaPseudoResizer},
    {"-webkit-scrollbar",             KatanaPseudoScrollbar},
    {"-webkit-scrollbar-button",      KatanaPseudoScrollbarButton},
    {"-webkit-scrollbar-corner",      KatanaPseudoScrollbarCorner},
    {"-webkit-scrollbar-thumb",       KatanaPseudoScrollbarThumb},
    {"-webkit-scrollbar-track",       KatanaPseudoScrollbarTrack},
    {"-webkit-scrollbar-track-piece", KatanaPseudoScrollbarTrackPiece},
    {"active",                        KatanaPseudoActive},
    {"after",                         KatanaPseudoAfter},
    {"backdrop",                      KatanaPseudoBackdrop},
    {"before",                        KatanaPseudoBefore},
    {"checked",                       KatanaPseudoChecked},
    {"content",                       KatanaPseudoContent},
    {"corner-present",                KatanaPseudoCornerPresent},
    {"cue",                           KatanaPseudoWebKitCustomElement},
    {"decrement",                     KatanaPseudoDecrement},
    {"default",                       KatanaPseudoDefault},
    {"disabled",                      KatanaPseudoDisabled},
    {"double-button",                 KatanaPseudoDoubleButton},
    {"empty",                         KatanaPseudoEmpty},
    {"enabled",                       KatanaPseudoEnabled},
    {"end",                           KatanaPseudoEnd},
    {"first",                         KatanaPseudoFirstPage},
    {"first-child",                   KatanaPseudoFirstChild},
    {"first-letter",                  KatanaPseudoFirstLetter},
    {"first-line",                    KatanaPseudoFirstLine},
    {"first-of-type",                 KatanaPseudoFirstOfType},
    {"focus",                         KatanaPseudoFocus},
    {"future",                        KatanaPseudoFutureCue},
    {"horizontal",                    KatanaPseudoHorizontal},
    {"host",                          KatanaPseudoHost},
    {"hover",                         KatanaPseudoHover},
    {"in-range",                      KatanaPseudoInRange},
    {"increment",                     KatanaPseudoIncrement},
    {"indeterminate",                 KatanaPseudoIndeterminate},
    {"invalid",                       KatanaPseudoInvalid},
    {"last-child",                    KatanaPseudoLastChild},
    {"last-of-type",                  KatanaPseudoLastOfType},
    {"left",                          KatanaPseudoLeftPage},
    {"link",                          KatanaPseudoLink},
    {"no-button",                     KatanaPseudoNoButton},
    {"only-child",                    KatanaPseudoOnlyChild},
    {"only-of-type",                  KatanaPseudoOnlyOfType},
    {"optional",                      KatanaPseudoOptional},
    {"out-of-range",                  KatanaPseudoOutOfRange},
    {"past",                          KatanaPseudoPastCue},
    {"read-only",                     KatanaPseudoReadOnly},
    {"read-write",                    KatanaPseudoReadWrite},
    {"required",                      KatanaPseudoRequired},
    {"right",                         KatanaPseudoRightPage},
    {"root",                          KatanaPseudoRoot},
    {"scope",                         KatanaPseudoScope},
    {"selection",                     KatanaPseudoSelection},
    {"shadow",                        KatanaPseudoShadow},
    {"single-button",                 KatanaPseudoSingleButton},
    {"start",                         KatanaPseudoStart},
    {"target",                        KatanaPseudoTarget},
    {"unresolved",                    KatanaPseudoUnresolved},
    {"valid",                         KatanaPseudoValid},
    {"vertical",                      KatanaPseudoVertical},
    {"visited",                       KatanaPseudoVisited},
    {"window-inactive",               KatanaPseudoWindowInactive},
};

const static KatanaNameToPseudoStruct kPseudoTypeWithArgumentsMap[] = {
    {"-webkit-any(",      KatanaPseudoAny},
    {"cue(",              KatanaPseudoCue},
    {"host(",             KatanaPseudoHost},
    {"host-context(",     KatanaPseudoHostContext},
    {"lang(",             KatanaPseudoLang},
    {"not(",              KatanaPseudoNot},
    {"nth-child(",        KatanaPseudoNthChild},
    {"nth-last-child(",   KatanaPseudoNthLastChild},
    {"nth-last-of-type(", KatanaPseudoNthLastOfType},
    {"nth-of-type(",      KatanaPseudoNthOfType},
};

static const KatanaNameToPseudoStruct* lower_bound(const KatanaNameToPseudoStruct *map,
                                                   size_t count, const char* key);

static KatanaPseudoType name_to_pseudo_type(const char* name, bool hasArguments)
{
    if (NULL == name)
        return KatanaPseudoUnknown;
    
    const KatanaNameToPseudoStruct* pseudoTypeMap;
    size_t count;
    
    if (hasArguments) {
        pseudoTypeMap = kPseudoTypeWithArgumentsMap;
        count = sizeof(kPseudoTypeWithArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
    } else {
        pseudoTypeMap = kPseudoTypeWithoutArgumentsMap;
        count = sizeof(kPseudoTypeWithoutArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
    }
    
    const KatanaNameToPseudoStruct* match = lower_bound(pseudoTypeMap, count, name);
    if ( match == (pseudoTypeMap + count)
         || 0 != stricmp(match->string, name) )
        return KatanaPseudoUnknown;
    
    return match->type;
}

static const KatanaNameToPseudoStruct* lower_bound(const KatanaNameToPseudoStruct *array,
                                                   size_t size, const char* key) {
    const KatanaNameToPseudoStruct* it;
    const KatanaNameToPseudoStruct* first = array;
    size_t count = size, step;
    while (count > 0) {
        it = first;
        step = count / 2;
        it += step;
        if (strncmp(it->string, key, strlen(key)) < 0) {
            first = ++it;
            count -= step + 1;
        } else count = step;
    }
    return first;
}

#if KATANA_PARSER_DEBUG

void test_lower_bound()
{
    const KatanaNameToPseudoStruct* pseudoTypeMap;
    size_t count;
    
    pseudoTypeMap = kPseudoTypeWithArgumentsMap;
    count = sizeof(kPseudoTypeWithArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
    
    for ( size_t i = 0; i < count; i++ ) {
        const KatanaNameToPseudoStruct* res = lower_bound(pseudoTypeMap, count, pseudoTypeMap[i].string);
        assert(pseudoTypeMap[i].type == res->type);
    }
    
    pseudoTypeMap = kPseudoTypeWithoutArgumentsMap;
    count = sizeof(kPseudoTypeWithoutArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
    
    for ( size_t i = 0; i < count; i++ ) {
        const KatanaNameToPseudoStruct* res = lower_bound(pseudoTypeMap, count, pseudoTypeMap[i].string);
        assert(pseudoTypeMap[i].type == res->type);
    }
}

#endif // #if KATANA_PARSER_DEBUG
