/*
 * Copyright (C) 2009 Esmertec AG.
 * Copyright (C) 2009 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"

bool WbxmlEncoder::isXmlWhitespace(int ch)
{
    return ch == ' ' || ch == 9 || ch == 0xd || ch == 0xa;
}

bool WbxmlEncoder::parseUint(const char * s, int len, uint32_t *res)
{
    string str(s, len);
    char *end;
    long long val = strtoll(str.c_str(), &end, 10);
    if (*end != 0 || val < 0 || val > 0xFFFFFFFFU) {
        return false;
    }
    *res = (uint32_t)val;
    return true;
}

EncoderError WbxmlEncoder::encodeInteger(const char *chars, int len)
{
    uint32_t val;
    if (!parseUint(chars, len, &val)) {
        return ERROR_INVALID_INTEGER_VALUE;
    }

    appendResult(TOKEN_OPAQUE);
    uint32_t mask = 0xff000000U;
    int numBytes = 4;
    while (!(val & mask) && mask) {
        numBytes--;
        mask >>= 8;
    }
    if (!numBytes) {
        // Zero value. We generate at least 1 byte OPAQUE data.
        // libwbxml2 generates 0 byte long OPAQUE data (0xC3 0x00) in this case.
        numBytes = 1;
    }

    appendResult(numBytes);
    while (numBytes) {
        numBytes--;
        appendResult((val >> (numBytes * 8)) & 0xff);
    }

    return NO_ERROR;
}

EncoderError WbxmlEncoder::encodeDatetime(const char *chars, int len)
{
    // to make life easier we accept only yyyymmddThhmmssZ
    if (len != 16 || chars[8] != 'T' || chars[15] != 'Z') {
        return ERROR_INVALID_DATETIME_VALUE;
    }
    appendResult(TOKEN_OPAQUE);
    appendResult(6);

    uint32_t year, month, day, hour, min, sec;
    if (!parseUint(chars, 4, &year)
            || !parseUint(chars + 4, 2, &month)
            || !parseUint(chars + 6, 2, &day)
            || !parseUint(chars + 9, 2, &hour)
            || !parseUint(chars + 11,2, &min)
            || !parseUint(chars + 13,2, &sec)) {
        return ERROR_INVALID_DATETIME_VALUE;
    }
    if (year > 4095 || month > 12 || day > 31 || hour > 23 || min > 59 || sec > 59) {
        return ERROR_INVALID_DATETIME_VALUE;
    }

    appendResult(year >> 6);
    appendResult(((year & 0x3f) << 2) | (month >> 2));
    appendResult(((month & 0x3) << 6) | (day << 1) | (hour >> 4));
    appendResult(((hour & 0xf) << 4) | (min >> 2));
    appendResult(((min & 0x2) << 6) | sec);
    appendResult('Z');
    return NO_ERROR;
}

void WbxmlEncoder::encodeInlinedStr(const char *s, int len)
{
    // TODO: handle ENTITY
    appendResult(TOKEN_STR_I);
    appendResult(s, len);
    appendResult('\0');
}

void WbxmlEncoder::encodeMbuint(uint32_t val)
{
    char buf[32 / 7 + 1];   // each byte holds up to 7 bits
    int i = sizeof(buf);

    buf[--i] = val & 0x7f;
    val >>= 7;
    while ((i > 0) && (val & 0x7f)) {
        buf[--i] = 0x80 | (val & 0x7f);
        val >>= 7;
    }

    appendResult(buf + i, sizeof(buf) - i);
}

int WbxmlEncoder::appendToStringTable(const char *s)
{
    int stringTableSize = mStringTable.size();
    int offset = 0;
    
    // search the string table to find if the string already exist
    int index = 0;
    for (; index < stringTableSize; index++) {
        if (mStringTable[index] == s) {
            break;
        }
        offset += mStringTable[index].length();
        ++offset; // '\0' for each string in the table
    }
    if (index == stringTableSize) {
        // not found, insert a new one
        mStringTable.push_back(s);
    }
    return offset;
}

void WbxmlEncoder::sendResult()
{
    if (mHandler) {
        string data;
        string tmp = mResult;
        mResult = data;

        // WBXML 1.3, UTF-8
        char header[3] = { 0x03, (char) mPublicId, 0x6A };
        appendResult(header, 3);

        // calculate the length of string table
        int len = 0;
        for (int i = 0; i < mStringTable.size(); i++) {
            len += mStringTable[i].length();
            ++len;
        }

        encodeMbuint(len);

        // encode each string in the table
        for (int i = 0; i < mStringTable.size(); i++) {
            mResult += mStringTable[i];
            mResult += '\0';
        }

        mResult += tmp;

        mHandler->wbxmlData(mResult.c_str(), mResult.size());
    }
}