/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The Initial Developer of this code is David Baum.
 * Portions created by David Baum are Copyright (C) 1999 David Baum.
 * All Rights Reserved.
 *
 * Portions created by John Hansen are Copyright (C) 2005 John Hansen.
 * All Rights Reserved.
 *
 */
#include <cstdio>
#include <cstring>
#include "RCX_Cmd.h"
#include "Program.h"

using std::printf;
using std::memcpy;

// handy macros
#define LO_BYTE(s)  ((UByte)((s) & 0xff))
#define HI_BYTE(s)  ((UByte)(((s) >> 8) & 0xff))

RCX_ValueType GetRcxValueType(int v)
{
    RCX_ValueType result = RCX_VALUE_TYPE(v);
    if (result == kRCX_VariableType || result == kRCX_IndirectType) {
        const RCX_Target *t = gProgram->GetTarget();
        if (t->fType == kRCX_SwanTarget) {
            result = (result == kRCX_VariableType)
                ? kRCX_GlobalVariableType : kRCX_IndirectGlobalVarType;
        }
    }
    return result;
}

RCX_Cmd::~RCX_Cmd()
{
    if (fLength > kRCX_Cmd_MaxShortLength)
        delete [] fBody.fPtr;
}


void RCX_Cmd::SetLength(int length)
{
    if (fLength == length) return;

    if (fLength > kRCX_Cmd_MaxShortLength)
        delete [] fBody.fPtr;

    fLength = length;

    if (fLength > kRCX_Cmd_MaxShortLength)
        fBody.fPtr = new UByte[fLength];
}


int RCX_Cmd::CopyOut(UByte *dst)
{
    memcpy(dst, GetBody(), (ULong)fLength);
    return fLength;
}


RCX_Cmd* RCX_Cmd::Set(const UByte *data, int length)
{
    SetLength(length);
    memcpy(GetBody(), data, (size_t)length);
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0)
{
    SetLength(1);
    fBody.fData[0] = d0;
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1)
{
    SetLength(2);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2)
{
    SetLength(3);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2, UByte d3)
{
    SetLength(4);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    fBody.fData[3] = d3;
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2, UByte d3, UByte d4)
{
    SetLength(5);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    fBody.fData[3] = d3;
    fBody.fData[4] = d4;
    return this;
}


RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2, UByte d3, UByte d4,
    UByte d5)
{
    SetLength(6);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    fBody.fData[3] = d3;
    fBody.fData[4] = d4;
    fBody.fData[5] = d5;
    return this;
}

RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2, UByte d3, UByte d4,
    UByte d5, UByte d6)
{
    SetLength(7);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    fBody.fData[3] = d3;
    fBody.fData[4] = d4;
    fBody.fData[5] = d5;
    fBody.fData[6] = d6;
    return this;
}

RCX_Cmd* RCX_Cmd::Set(UByte d0, UByte d1, UByte d2, UByte d3, UByte d4,
    UByte d5, UByte d6, UByte d7)
{
    SetLength(8);
    fBody.fData[0] = d0;
    fBody.fData[1] = d1;
    fBody.fData[2] = d2;
    fBody.fData[3] = d3;
    fBody.fData[4] = d4;
    fBody.fData[5] = d5;
    fBody.fData[6] = d6;
    fBody.fData[7] = d7;
    return this;
}


RCX_Cmd* RCX_Cmd::MakeGetVersions()
{
    return Set(kRCX_GetVersions, 1, 3, 5, 7, 0xb);
}


RCX_Cmd* RCX_Cmd::MakePlayTone(UShort freq, UByte duration)
{
    return Set(kRCX_PlayToneOp, LO_BYTE(freq), HI_BYTE(freq), duration);
}


RCX_Cmd* RCX_Cmd::MakeBegin(RCX_ChunkType type, UByte taskNumber, UShort length)
{
    UByte op = (UByte)((type == kRCX_TaskChunk) ? kRCX_BeginTaskOp : kRCX_BeginSubOp);
    return Set(op, 0, taskNumber, 0, LO_BYTE(length), HI_BYTE(length));
}


RCX_Cmd* RCX_Cmd::MakeOutputPower(UByte outputs, RCX_Value value)
{
    return Set(kRCX_OutputPowerOp, outputs, GetRcxValueType(value), (UByte)RCX_VALUE_DATA(value));
}


RCX_Cmd* RCX_Cmd::MakeValue16(UByte op, RCX_Value value)
{
    int data = RCX_VALUE_DATA(value);

    return Set(op, GetRcxValueType(value), LO_BYTE(data), HI_BYTE(data));
}

RCX_Cmd* RCX_Cmd::MakeValue8(UByte op, RCX_Value value)
{
    return Set(op, GetRcxValueType(value), (UByte)RCX_VALUE_DATA(value));
}

RCX_Cmd* RCX_Cmd::MakeSet(RCX_Value dst, RCX_Value src)
{
    UByte dType = GetRcxValueType(dst);

    SetLength(6);
    UByte *ptr = GetBody();

    *ptr++ = kRCX_SetSrcValueOp;
    *ptr++ = dType;
    *ptr++ = LO_BYTE(RCX_VALUE_DATA(dst));
    *ptr++ = GetRcxValueType(src);
    *ptr++ = LO_BYTE(RCX_VALUE_DATA(src));
    *ptr   = HI_BYTE(RCX_VALUE_DATA(src));

    return this;
}

RCX_Cmd* RCX_Cmd::MakeSetMath(RCX_Value dst, RCX_Value src, RCX_VarCode op)
{
    // this method is only for firmwares which have math source value opcodes
    // (only Swan at this point)
    SetLength(7);
    UByte *ptr = GetBody();
    RCX_ValueType vtDst = GetRcxValueType(dst);
    if (vtDst == kRCX_VariableType          ||
        vtDst == kRCX_IndirectType          ||
        vtDst == kRCX_GlobalVariableType    ||
        vtDst == kRCX_IndirectGlobalVarType ||
        vtDst == kRCX_IndexedArrayType)
    {
        switch(op) {
            case kRCX_AddVar: *ptr++ = rcxAddToSourceValue;         break;
            case kRCX_SubVar: *ptr++ = rcxMinusToSourceValue;       break;
            case kRCX_DivVar: *ptr++ = rcxDivideToSourceValue;      break;
            case kRCX_MulVar: *ptr++ = rcxTimesToSourceValue;       break;
            case kRCX_SgnVar: *ptr++ = rcxSignValueSourceValue;     break;
            case kRCX_AbsVar: *ptr++ = rcxAbsoluteValueSourceValue; break;
            case kRCX_AndVar: *ptr++ = rcxAndToSourceValue;         break;
            case kRCX_OrVar : *ptr++ = rcxOrToSourceValue;          break;
            case kRCX_NotVar: *ptr++ = rcxBitComplementSourceValue; break;
            case kRCX_ModVar: *ptr++ = rcxModuloToSourceValue;      break;
            case kRCX_ShlVar: *ptr++ = rcxShiftLeft;                break;
            case kRCX_ShrVar: *ptr++ = rcxShiftRight;               break;
            case kRCX_XOrVar: *ptr++ = rcxXOr;                      break;
            default :         *ptr++ = rcxAssignSourceValue;        break;
        }
    } else {
        // the destination source is not a "RAM memory variable" source so use the
        // generic source opcodes instead
        switch(op) {
            case kRCX_AddVar: *ptr++ = rcxAddToGenericParms;         break;
            case kRCX_SubVar: *ptr++ = rcxMinusToGenericParms;       break;
            case kRCX_DivVar: *ptr++ = rcxDivideToGenericParms;      break;
            case kRCX_MulVar: *ptr++ = rcxTimesToGenericParms;       break;
            case kRCX_SgnVar: *ptr++ = rcxSignValueSourceValue;      break;
            case kRCX_AbsVar: *ptr++ = rcxAbsoluteValueSourceValue;  break;
            case kRCX_AndVar: *ptr++ = rcxAndToGenericParms;         break;
            case kRCX_OrVar : *ptr++ = rcxOrToGenericParms;          break;
            case kRCX_NotVar: *ptr++ = rcxBitComplementGenericParms; break;
            case kRCX_ModVar: *ptr++ = rcxModuloToGenericParms;      break;
            case kRCX_ShlVar: *ptr++ = rcxShiftLeft;                 break;
            case kRCX_ShrVar: *ptr++ = rcxShiftRight;                break;
            case kRCX_XOrVar: *ptr++ = rcxXOr;                       break;
            default :         *ptr++ = rcxAssignSourceValue;         break;
        }
    }

    *ptr++ = vtDst;
    *ptr++ = LO_BYTE(RCX_VALUE_DATA(dst));
    *ptr++ = HI_BYTE(RCX_VALUE_DATA(dst));
    *ptr++ = GetRcxValueType(src);
    *ptr++ = LO_BYTE(RCX_VALUE_DATA(src));
    *ptr   = HI_BYTE(RCX_VALUE_DATA(src));

    return this;
}

RCX_Cmd* RCX_Cmd::MakeTest(RCX_Value v1, RCX_Relation rel, RCX_Value v2, short offset)
{
    int d1 = RCX_VALUE_DATA(v1);
    UByte *ptr;
    SetLength(8);

    ptr = GetBody();

    *ptr++ = kRCX_TestOp;
    *ptr++ = (UByte)((rel << 6) | GetRcxValueType(v1));
    *ptr++ = GetRcxValueType(v2);
    *ptr++ = LO_BYTE(d1);
    *ptr++ = HI_BYTE(d1);
    *ptr   = (UByte)RCX_VALUE_DATA(v2);
    SetOffset(offset);
    return this;
}


RCX_Cmd* RCX_Cmd::MakeCheckLoop(short offset)
{
    Set(kRCX_CheckLoopOp, 0, 0);
    SetOffset(offset);
    return this;
}


RCX_Cmd* RCX_Cmd::MakeJump(short offset)
{
    SetLength(3);

    GetBody()[0] = kRCX_JumpOp;
    SetOffset(offset);
    return this;
}


void RCX_Cmd::SetOffset(int offset)
{
    UByte *ptr;
    UByte neg = 0;

    offset -= fLength - 2;
    ptr = GetBody() + fLength - 2;

    if (GetBody()[0] != kRCX_JumpOp) {
        *ptr++ = (UByte)(offset & 0xff);
        *ptr   = (UByte)((offset >> 8) & 0xff);
    } else {
        if (offset < 0) {
            neg = 0x80;
            offset = -offset;
        }

        *ptr++ = (UByte)(neg | (offset & 0x7f));
        *ptr   = (UByte)((offset >> 7) & 0xff);
    }
}


RCX_Cmd* RCX_Cmd::MakeDownload(UShort seq, const UByte *data, UShort length)
{
    UByte *ptr;
    UByte checksum = 0;

    SetLength(6 + length);

    ptr = GetBody();
    *ptr++ = kRCX_DownloadOp;
    *ptr++ = LO_BYTE(seq);
    *ptr++ = HI_BYTE(seq);
    *ptr++ = LO_BYTE(length);
    *ptr++ = HI_BYTE(length);

    while (length--) {
        UByte b = *data++;
        checksum += b;
        *ptr++ = b;
    }

    *ptr = checksum;
    return this;
}


RCX_Cmd* RCX_Cmd::MakeUploadDatalog(UShort start, UShort count)
{
    return Set(kRCX_UploadDatalogOp,
        LO_BYTE(start), HI_BYTE(start),
            LO_BYTE(count), HI_BYTE(count));
}


RCX_Cmd* RCX_Cmd::MakeVarNewSrc(RCX_VarCode code, UByte var, RCX_Value value)
{
    int data = RCX_VALUE_DATA(value);
    RCX_ValueType dsrc = GetRcxValueType(value);
    if (code == kRCX_SgnVar || code == kRCX_AbsVar || code >= kRCX_ShlVar) {
        UByte x;
        switch (code) {
          case kRCX_SgnVar : x = rcxSignValueSourceValue;     break;
          case kRCX_AbsVar : x = rcxAbsoluteValueSourceValue; break;
          case kRCX_ShlVar : x = rcxShiftLeft;                break;
          case kRCX_ShrVar : x = rcxShiftRight;               break;
          default          : x = rcxXOr;                      break;
        }
        return Set(x, kRCX_GlobalVariableType, var, 0x00, dsrc, LO_BYTE(data), HI_BYTE(data));
    }
    else {
        // use global variable source and associated opcodes here
        const UByte opcodes[2][11][2] =
        {
          // non-constant operand
          { {rcxAssignGlobalVariable, rcxAssignSourceValue},
            {rcxAddToGlobal, rcxAddToSourceValue},
            {rcxMinusToGlobal, rcxMinusToSourceValue},
            {rcxDivideToGlobal, rcxDivideToSourceValue},
            {rcxTimesToGlobal, rcxTimesToSourceValue}, {0, 0}, {0, 0},
            {rcxAndToGlobal, rcxAndToSourceValue},
            {rcxOrToGlobal, rcxOrToSourceValue},
            {rcxBitComplementGlobal, rcxBitComplementSourceValue},
            {rcxModuloToGlobal, rcxModuloToSourceValue} },
          // constant operand
          { {rcxAssignGlobalConstantByte, rcxAssignGlobalConstant},
            {rcxAddToGlobalUbyte, rcxAddToGlobalConstant},
            {rcxMinusToGlobalUbyte, rcxMinusToGlobalConstant},
            {rcxDivideToGlobalUbyte, rcxDivideToGlobalConstant},
            {rcxTimesToGlobalUbyte, rcxTimesToGlobalConstant}, {0, 0}, {0, 0},
            {rcxAndToGlobalUbyte, rcxAndToGlobalConstant},
            {rcxOrToGlobalUbyte, rcxOrToGlobalConstant},
            {rcxBitComplementGlobalByte, rcxBitComplementGlobalConstant},
            {rcxModuloToGlobalUbyte, rcxModuloToGlobalConstant} }
        };
        UByte x = opcodes[dsrc == kRCX_ConstantType][code][HI_BYTE(data) != 0];
        if (dsrc == kRCX_ConstantType) {
            if (HI_BYTE(data) == 0)
              return Set(x, var, LO_BYTE(data));
            else
              return Set(x, var, HI_BYTE(data), LO_BYTE(data));
        } else {
            if (HI_BYTE(data) == 0)
              return Set(x, var, dsrc, LO_BYTE(data));
            else
              return Set(x, kRCX_GlobalVariableType, var, 0x00, dsrc, LO_BYTE(data), HI_BYTE(data));
        }
    }
}


RCX_Cmd* RCX_Cmd::MakeVar(RCX_VarCode code, UByte var, RCX_Value value)
{
    const RCX_Target *t = gProgram->GetTarget();
    if (t->fHasExtendedMathOps)
        return MakeVarNewSrc(code, var, value);

    int data = RCX_VALUE_DATA(value);
    return Set((UByte)kRCX_VarOp(code), var, GetRcxValueType(value), LO_BYTE(data), HI_BYTE(data));
}


RCX_Cmd* RCX_Cmd::MakeUnlock()
{
    static const UByte unlockMsg[] = {kRCX_UnlockOp,
        'L', 'E', 'G', 'O', 174
    };
    Set(unlockMsg, sizeof(unlockMsg));

    return this;
}


RCX_Cmd* RCX_Cmd::MakeUnlockCM()
{
    static const UByte unlockMsg[] = {kRCX_UnlockOp,
      'D','o',' ','y','o','u',' ','b','y','t','e',',',' ',
      'w','h','e','n',' ','I',' ','k','n','o','c','k','?'
    };
    Set(unlockMsg, sizeof(unlockMsg));

    return this;
}


void RCX_Cmd::Print()
{
    UByte *ptr = GetBody();

    for(int i=0; i<fLength; i++) {
        printf("%02x ", ptr[i]);
    }

    printf("\n");
}
