/* ParseBinary.cpp

   Copyright (C)  David C. J. Matthews 2004  dm at prolingua.co.uk

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
   Or, point your browser to http://www.gnu.org/copyleft/gpl.html

*/

/*
Parser for ASN1 binary notation.  Does minimal syntax checking, assuming that this will already have
been done before the binary was produced.  Creates a MHParseNode tree structure.
*/

#include "Engine.h"
#include "ParseBinary.h"
#include "ASN1Codes.h"
#include "ParseNode.h"
#include "BaseClasses.h"
#include "Root.h"
#include "Groups.h"
#include "Logging.h"

MHParseBinary::MHParseBinary(QByteArray &program)
{
    m_data = program;
    m_p = 0;
}

#define INDEFINITE_LENGTH   (-1)

// Get the next byte.  In most all cases it's an error if we reach end-of-file
// and we throw an exception.
unsigned char MHParseBinary::GetNextChar()
{
    if (m_p >= (int)m_data.size()) MHERROR("Unexpected end of file");
    return m_data[m_p++];
}


// Parse a string argument.  ASN1 strings can include nulls as valid characters.
void MHParseBinary::ParseString(int endStr, MHOctetString &str)
{
     // TODO: Don't deal with indefinite length at the moment.
    if (endStr == INDEFINITE_LENGTH)
        MHERROR("Indefinite length strings are not implemented");
    int nLength = endStr - m_p;
    unsigned char *stringValue = (unsigned char*)malloc(endStr - m_p);
    unsigned char *p = stringValue;
    while (m_p < endStr) *p++ = GetNextChar();
    str.Copy(MHOctetString((const char *)stringValue, nLength));
    free(stringValue);
}

// Parse an integer argument.  Also used for bool and enum.
int MHParseBinary::ParseInt(int endInt)
{
    int intVal = 0;
    bool firstByte = true;
    if (endInt == INDEFINITE_LENGTH)
        MHERROR("Indefinite length integers are not implemented");
    while (m_p < endInt) {
        unsigned char ch = GetNextChar();
        // Integer values are signed so if the top bit is set in the first byte
        // we need to set the sign bit.
        if (firstByte && ch >= 128) intVal = -1;
        firstByte = false;
        intVal = (intVal << 8) | ch;
    }
    return intVal;
}


//  Simple recursive parser for ASN1 BER.
MHParseNode *MHParseBinary::DoParse()
{
    unsigned char ch;
    // Tag class
    enum { Universal, Context/*, Pseudo*/ } tagClass = Universal;
    // Byte count of end of this item.  Set to INDEFINITE_LENGTH if the length is Indefinite.
    int endOfItem;
    unsigned int tagNumber = 0;

    // Read the first character.
    ch = GetNextChar();

    // ASN1 Coding rules: Top two bits (0 and 1) indicate the tag class.
    // 0x00 - Universal,  0x40 - Application, 0x80 - Context-specific, 0xC0 - Private
    // We only use Universal and Context.
    switch (ch & 0xC0) {
    case 0x00: // Universal
        tagClass = Universal;
        break;
    case 0x80:
        tagClass = Context;
        break;
    default:
        MHERROR(QString("Invalid tag class = %1").arg(ch, 0, 16));
    }
    // Bit 2 indicates whether it is a simple or compound type.  Not used.
    // Lower bits are the tag number.
    tagNumber = ch & 0x1f;
    if (tagNumber == 0x1f) { // Except that if it is 0x1F then the tag is encoded in the following bytes.
        tagNumber = 0;
        do {
            ch = GetNextChar();
            tagNumber = (tagNumber << 7) | (ch & 0x7f);
        } while (ch & 0x80); // Top bit set means there's more to come.
    }

    // Next byte is the length.  If it is less than 128 it is the actual length, otherwise it
    // gives the number of bytes containing the length, except that if this is zero the item
    // has an "indefinite" length and is terminated by two zero bytes.
    ch = GetNextChar();
    if (ch & 0x80) {
        int lengthOfLength = ch & 0x7f;
        if (lengthOfLength == 0) endOfItem = INDEFINITE_LENGTH;
        else {
            endOfItem = 0;
            while (lengthOfLength--) {
                ch = GetNextChar();
                endOfItem = (endOfItem << 8) | ch;
            }
            endOfItem += m_p;
        }
    }
    else endOfItem = ch + m_p;

    if (tagClass == Context) {
        MHPTagged *pNode = new MHPTagged(tagNumber);
        try {
            // The argument here depends on the particular tag we're processing.
            switch (tagNumber) {
            case C_MULTIPLE_SELECTION:
            case C_OBSCURED_INPUT:
            case C_INITIALLY_AVAILABLE:
            case C_WRAP_AROUND:
            case C_TEXT_WRAPPING:
            case C_INITIALLY_ACTIVE:
            case C_MOVING_CURSOR:
            case C_SHARED:
            case C_ENGINE_RESP:
            case C_TILING:
            case C_BORDERED_BOUNDING_BOX:
                { // BOOL
                    // If there is no argument we need to indicate that so that it gets
                    // the correct default value.
                    if (m_p != endOfItem){
                        int intVal = ParseInt(endOfItem); // May raise an exception
                        pNode->AddArg(new MHPBool(intVal != 0));
                    }
                    break;
                }

            case C_INPUT_TYPE:
            case C_SLIDER_STYLE:
            case C_TERMINATION:
            case C_ORIENTATION:
            case C_HORIZONTAL_JUSTIFICATION:
            case C_BUTTON_STYLE:
            case C_START_CORNER:
            case C_LINE_ORIENTATION:
            case C_VERTICAL_JUSTIFICATION:
            case C_STORAGE:
                { // ENUM
                    if (m_p != endOfItem){
                        int intVal = ParseInt(endOfItem); // May raise an exception
                        pNode->AddArg(new MHPEnum(intVal));
                    }
                }

            case C_INITIAL_PORTION:
            case C_STEP_SIZE:
            case C_INPUT_EVENT_REGISTER:
            case C_INITIAL_VALUE:
            case C_IP_CONTENT_HOOK:
            case C_MAX_VALUE:
            case C_MIN_VALUE:
            case C_LINE_ART_CONTENT_HOOK:
            case C_BITMAP_CONTENT_HOOK:
            case C_TEXT_CONTENT_HOOK:
            case C_STREAM_CONTENT_HOOK:
            case C_MAX_LENGTH:
            case C_CHARACTER_SET:
            case C_ORIGINAL_TRANSPARENCY:
            case C_ORIGINAL_GC_PRIORITY:
            case C_LOOPING:
            case C_ORIGINAL_LINE_STYLE:
            case C_STANDARD_VERSION:
            case C_ORIGINAL_LINE_WIDTH:
            case C_CONTENT_HOOK:
            case C_CONTENT_CACHE_PRIORITY:
            case C_COMPONENT_TAG:
            case C_ORIGINAL_VOLUME:
            case C_PROGRAM_CONNECTION_TAG:
            case C_CONTENT_SIZE:
                { // INT
                    if (m_p != endOfItem){
                        int intVal = ParseInt(endOfItem); // May raise an exception
                        pNode->AddArg(new MHPInt(intVal));
                    }
                }

            case C_OBJECT_INFORMATION:
            case C_CONTENT_REFERENCE:
            case C_FONT_ATTRIBUTES:
            case C_CHAR_LIST:
            case C_NAME:
            case C_ORIGINAL_LABEL:
                { // STRING
                    // Unlike INT, BOOL and ENUM we can't distinguish an empty string
                    // from a missing string.
                    MHOctetString str;
                    ParseString(endOfItem, str);
                    pNode->AddArg(new MHPString(str));
                }

            default:
                {
                    // Everything else has either no argument or is self-describing
                    // TODO: Handle indefinite length.
                    if (endOfItem == INDEFINITE_LENGTH)
                        MHERROR("Indefinite length arguments are not implemented");
                    while (m_p < endOfItem) {
                        pNode->AddArg(DoParse());
                    }
                }
            }
        }
        catch (...) {
            // Memory clean-up
            delete pNode;
            throw;
        }
        return pNode;
    }
    else { // Universal - i.e. a primitive type.
        // Tag values

        switch (tagNumber) {
        case U_BOOL: // Boolean
            {
                int intVal = ParseInt(endOfItem);
                return new MHPBool(intVal != 0);     
            }
        case U_INT: // Integer
            {
                int intVal = ParseInt(endOfItem);
                return new MHPInt(intVal);
            }
        case U_ENUM: // ENUM
            {
                int intVal = ParseInt(endOfItem);
                return new MHPEnum(intVal);
            }
        case U_STRING: // String
            {
                MHOctetString str;
                ParseString(endOfItem, str);
                return new MHPString(str);
            }
        case U_NULL: // ASN1 NULL
            {
                return new MHPNull;
            }
        case U_SEQUENCE: // Sequence
            {
                MHParseSequence *pNode = new MHParseSequence();
                if (endOfItem == INDEFINITE_LENGTH)
                    MHERROR("Indefinite length sequences are not implemented");
                try {
                    while (m_p < endOfItem) {
                        pNode->Append(DoParse());
                    }
                }
                catch (...) {
                    // Memory clean-up if error.
                    delete pNode;
                    throw;
                }
                return pNode;
            }
        default:
            MHERROR(QString("Unknown universal %1").arg(tagNumber));
        }
    }
}
