/*
 * Copyright (C) 2007 Esmertec AG.
 * Copyright (C) 2007 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 <stdio.h>
#include <stdlib.h>
#include "imps_encoder.h"
#include "csp13_hash.h"

/* TODOs:
 * - use string table?
 * - move common WBXML routines to WbxmlEncoder
 * - so called "token" based IMPS value encoding
 */

struct XmlnsPrefix {
    const char * prefix;
    int attrToken;
};
static const XmlnsPrefix csp13xmlns[] = {
    { "http://www.wireless-village.org/CSP",        0x05 },
    { "http://www.wireless-village.org/PA",         0x06 },
    { "http://www.wireless-village.org/TRC",        0x07 },
    { "http://www.openmobilealliance.org/DTD/WV-CSP",   0x08 },
    { "http://www.openmobilealliance.org/DTD/WV-PA",    0x09 },
    { "http://www.openmobilealliance.org/DTD/WV-TRC",   0x0a },
    { "http://www.openmobilealliance.org/DTD/IMPS-CSP", 0x0b },
    { "http://www.openmobilealliance.org/DTD/IMPS-PA",  0x0c },
    { "http://www.openmobilealliance.org/DTD/IMPS-TRC", 0x0d },
};

static bool isDatetimeElement(const char *name)
{
    return (strcmp("DateTime", name) == 0 || strcmp("DeliveryTime", name) == 0);
}

void ImpsWbxmlEncoder::reset()
{
    clearResult();

    mTagCodePage = 0;
    mCurrElement.clear();
    mDepth = 0;
}

EncoderError ImpsWbxmlEncoder::startElement(const char *name, const char **atts)
{
    if (name == NULL) {
        return ERROR_INVALID_DATA;
    }

    bool isUnknownTag = false;
    int stag = csp13TagNameToKey(name);
    if (stag == -1) {
        stag = TOKEN_LITERAL;
        isUnknownTag = true;
    }
    mDepth++;
    mCurrElement = name;

    if (((stag >> 8) & 0xff) != mTagCodePage) {
        // SWITCH_PAGE
        mTagCodePage = (stag >> 8) & 0xff;
        appendResult(TOKEN_SWITCH_PAGE);
        appendResult(mTagCodePage);
    }
    stag &= 0xff;
    stag |= 0x40;       // TODO: assuming we always have content

    if (atts && atts[0]) {
        stag |= 0x80;   // has attribute
    }
    appendResult(stag);

    if (isUnknownTag) {
        int index = appendToStringTable(name);
        encodeMbuint(index);
    }
    if (stag & 0x80) {
        for (size_t i = 0; atts[i]; i += 2) {
            EncoderError err = encodeAttrib(atts[i], atts[i + 1]);
            if (err != NO_ERROR) {
                return err;
            }
        }
        appendResult(TOKEN_END);
    }
    return NO_ERROR;
}

EncoderError ImpsWbxmlEncoder::characters(const char *chars, int len)
{
    if (chars == NULL || len < 0) {
        return ERROR_INVALID_DATA;
    }
    if (!len) {
        return NO_ERROR;
    }
    while (len && isXmlWhitespace(*chars)) {
        chars++;
        len--;
    }
    while (len && isXmlWhitespace(chars[len - 1])) {
        len--;
    }
    if (!len) {
        return NO_ERROR;
    }

    if (csp13IsIntegerTag(mCurrElement.c_str())) {
        return encodeInteger(chars, len);
    } else if (isDatetimeElement(mCurrElement.c_str())) {
        return encodeDatetime(chars, len);
    } else {
        return encodeString(chars, len);
    }
}

EncoderError ImpsWbxmlEncoder::opaque(const char *chars, int len)
{
    if (chars == NULL || len < 0) {
        return ERROR_INVALID_DATA;
    }
    if (!len) {
        return NO_ERROR;
    }
    appendResult(TOKEN_OPAQUE);
    encodeMbuint((uint32_t)len);
    appendResult(chars, len);
    return NO_ERROR;
}

EncoderError ImpsWbxmlEncoder::endElement()
{
    mDepth--;
    if (mDepth < 0) {
        return ERROR_INVALID_END_ELEMENT;
    }
    appendResult(TOKEN_END);
    mCurrElement.clear();
    if (mDepth == 0) {
        sendResult();
    }
    return NO_ERROR;
}

EncoderError ImpsWbxmlEncoder::encodeString(const char *chars, int len)
{
    // FIXME: should match and replace based on tokens (words)
    int token = csp13ValueTokenToKey(chars, len);
    if (token == -1) {
        encodeInlinedStr(chars, len);
    } else {
        appendResult(TOKEN_EXT_T_0);
        encodeMbuint(token);
    }
    return NO_ERROR;
}

EncoderError ImpsWbxmlEncoder::encodeAttrib(const char *name, const char *value)
{
    // IMPS so far has only "xmlns" attribute.
    // TODO: rewrite in a more generic way and move this to WbxmlEncoder
    if (strcmp(name, "xmlns")) {
        return ERROR_UNSUPPORTED_ATTR;
    }
    int valueLen = strlen(value);
    size_t csp13xmlnsCount = sizeof(csp13xmlns) / sizeof(csp13xmlns[0]);
    size_t i;
    for (i = 0; i < csp13xmlnsCount; i++) {
        const char * prefix = csp13xmlns[i].prefix;
        int prefixLen = strlen(csp13xmlns[i].prefix);
        if (strncmp(prefix, value, prefixLen) == 0) {
            appendResult(csp13xmlns[i].attrToken);
            if (valueLen > prefixLen) {
                encodeInlinedStr(value + prefixLen, valueLen - prefixLen);
            }
            return NO_ERROR;
        }
    }
    if (i == csp13xmlnsCount) {
        // not predefined attribute
        appendResult(TOKEN_LITERAL);
        int index = appendToStringTable(name);
        encodeMbuint(index);
    }
    encodeInlinedStr(value, valueLen);
    return NO_ERROR;
}