/*
 * 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 "xml2wbxml.h"
#include "wbxml_stl.h"
#include "imps_encoder.h"

struct PublicIdData {
    const char * pubid;
    const char * xmlns;
    PublicId idVal;
};

// http://www.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.htm
static const PublicIdData knownPublicId[] = {
    { "-//OMA//DTD IMPS-CSP 1.3//EN",
        "http://www.openmobilealliance.org/DTD/IMPS-CSP1.3",
        PUBLICID_IMPS_1_3 },
    { "-//OMA//DTD WV-CSP 1.2//EN",
        "http://www.openmobilealliance.org/DTD/WV-CSP1.2",
        PUBLICID_IMPS_1_2 },
    { "-//WIRELESSVILLAGE//DTD CSP 1.1//EN",
        "http://www.wireless-village.org/CSP1.1",
        PUBLICID_IMPS_1_1 },
    /*
    { "-//OMA//DRM 2.1//EN", XXX,           0x13 },
    { "-//OMA//DTD DRMREL 1.0//EN", XXX,    0x0e },
    { "-//SYNCML//DTD DevInf 1.2//EN", XXX, 0x1203 },
    { "-//SYNCML//DTD MetaInf 1.2//EN", XXX,0x1202 },
    { "-//SYNCML//DTD SyncML 1.2//EN", XXX, 0x1201 }
    */
};

static WbxmlEncoder * makeEncoder(int publicid)
{
    switch (publicid) {
        case PUBLICID_IMPS_1_3:
        case PUBLICID_IMPS_1_2:
        case PUBLICID_IMPS_1_1:
            return new ImpsWbxmlEncoder(publicid);

        default:
            return NULL;
    }
}

Xml2WbxmlEncoder::Xml2WbxmlEncoder() :
    mWbxmlHandler(NULL),
    mEncoder(NULL),
    mPublicId(-1),
    mDepth(0),
    mErrorCode(NO_ERROR)
{
    mExpatParser = new ExpatParser();
    mExpatParser->setContentHandler(this);
}

Xml2WbxmlEncoder::~Xml2WbxmlEncoder()
{
    delete(mExpatParser);
    delete(mEncoder);
}

void Xml2WbxmlEncoder::setWbxmlHandler(WbxmlHandler * handler)
{
    mWbxmlHandler = handler;
}

void Xml2WbxmlEncoder::startElement(const char *name, const char **atts)
{
    if (getErrorCode() != NO_ERROR)
        return;

    if (!mDepth) {
        if (!isPublicIdSet()) {
            for (int i = 0; atts[i]; i += 2) {
                // TODO: for now we don't handle xmlns:<prefix> yet
                if (strcmp(atts[i], "xmlns") == 0) {
                    if (detectPublicIdByXmlns(atts[i + 1]))
                        break;
                }
            }
        }
        if (isPublicIdSet()) {
            // TODO: at present fixed to WBXML 1.3, UTF-8, no string table
            mEncoder = makeEncoder(mPublicId);
            if (mEncoder == NULL) {
                setError(ERROR_UNSUPPORTED_DOCTYPE);
                return;
            }
            mEncoder->setWbxmlHandler(mWbxmlHandler);
        } else {
            setError(ERROR_NO_PUBLIC_ID);
            return;
        }
    }
    setError(mEncoder->startElement(name, atts));
    mDepth++;
}

void Xml2WbxmlEncoder::endElement(const char *name)
{
    if (getErrorCode() != NO_ERROR)
        return;

    setError(mEncoder->endElement());
    mDepth--;
}

void Xml2WbxmlEncoder::characters(const char *data, int len)
{
    if (getErrorCode() != NO_ERROR)
        return;

    setError(mEncoder->characters(data, len));
}

void Xml2WbxmlEncoder::startDoctype(const char *doctypeName,
        const char *sysid,
        const char *pubid,
        int has_internal_subset)
{
    if (!isPublicIdSet()) {
        detectPublicId(pubid);
    }
}

bool Xml2WbxmlEncoder::detectPublicId(const char * pubid)
{
    for (size_t i = 0; i < sizeof(knownPublicId) / sizeof(knownPublicId[0]); i++) {
        if (strcmp(pubid, knownPublicId[i].pubid) == 0) {
            //printf ("pubid %s => 0x%x\n", pubid, knownPublicId[i].idVal);
            setPublicId(knownPublicId[i].idVal);
            return true;
        }
    }
    return false;
}

bool Xml2WbxmlEncoder::detectPublicIdByXmlns(const char * xmlnsUri)
{
    for (size_t i = 0; i < sizeof(knownPublicId) / sizeof(knownPublicId[0]); i++) {
        if (strcmp(xmlnsUri, knownPublicId[i].xmlns) == 0) {
            //printf ("xmlns %s => 0x%x\n", xmlnsUri, knownPublicId[i].idVal);
            setPublicId(knownPublicId[i].idVal);
            return true;
        }
    }
    return false;
}

int Xml2WbxmlEncoder::encode(const char * data, uint32_t dataLen, bool end)
{
    if (data == NULL) {
        return WBXML_STATUS_ERROR;
    }
    if (mExpatParser->parse(data, (int)dataLen, end) != XML_STATUS_OK) {
        //printf ("Expat error: %s\n", XML_ErrorString(XML_GetErrorCode(mExpat)));
        return WBXML_STATUS_ERROR;
    }
    return getErrorCode() == NO_ERROR ? WBXML_STATUS_OK : WBXML_STATUS_ERROR;
}