// (C) Copyright 1996-2008 by Autodesk, Inc. 
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted, 
// provided that the above copyright notice appears in all copies and 
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting 
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC. 
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to 
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
// Description:
//
// This program demonstrates the use of the AcDBObject Xdata
// member functions.

#if defined(_DEBUG) && !defined(AC_FULL_DEBUG)
#error _DEBUG should not be defined except in internal Adesk debug builds
#endif

#include <stdlib.h>
#include <string.h>
#include <rxobject.h>
#include <rxregsvc.h>
#include <aced.h>
#include <dbsymtb.h>
#include <adslib.h>
#include "tchar.h"

#include "acestext.h"



void         printXdata();
void         addXdata();
void         printList(struct resbuf* pRb);
AcDbObject*  selectObject(AcDb::OpenMode openMode);
void         initApp();
void         unloadApp();
extern "C"
AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode, void*);

// THE FOLLOWING CODE APPEARS IN THE SDK DOCUMENT.

// This function calls the
// selectObject() function to allow the user to pick an
// object; then it accesses the xdata of the object and
// sends the list to the printList() function that lists the
// restype and resval values.
// 
void
printXdata()
{
    // Select and open an object.
    //
    AcDbObject *pObj;
    if ((pObj = selectObject(AcDb::kForRead)) == NULL) {
        return;
    }

    // Get the application name for the xdata.
    //
    TCHAR appname[133];
    if (acedGetString(NULL,
        _T("\nEnter the desired Xdata application name: "),
        appname) != RTNORM)
    {
        return;
    }

    // Get the xdata for the application name.
    //
    struct resbuf *pRb;
    pRb = pObj->xData(appname);

    if (pRb != NULL) {

        // Print the existing xdata if any is present.
        // Notice that there is no -3 group, as there is in
        // LISP. This is ONLY the xdata, so
        // the -3 xdata-start marker isn't needed.
        // 
        printList(pRb);
        acutRelRb(pRb);

    } else {
        acutPrintf(_T("\nNo xdata for this appname"));
    }
    pObj->close();
}

void 
addXdata() 
{
    AcDbObject* pObj = selectObject(AcDb::kForRead);
    if (!pObj) {
        acutPrintf(_T("Error selecting object\n"));
        return;
    }

    
    // Get the application name and string to be added to
    // xdata.
    //
    TCHAR appName[132], resString[200];
    appName[0] = resString[0] = _T('\0');

    acedGetString(NULL, _T("Enter application name: "),
        appName);
    acedGetString(NULL, _T("Enter string to be added: "),
        resString);

    
    struct  resbuf  *pRb, *pTemp;
    
    pRb = pObj->xData(appName);

    if (pRb != NULL) {

        // If xdata is present, then walk to the
        // end of the list.
        //
        for (pTemp = pRb; pTemp->rbnext != NULL;
                pTemp = pTemp->rbnext)
                { ; }
    } else {
        // If xdata is not present, register the application
        // and add appName to the first resbuf in the list.
        // Notice that there is no -3 group as there is in
        // AutoLISP. This is ONLY the xdata so
        // the -3 xdata-start marker isn't needed.
        // 
        acdbRegApp(appName);

        pRb = acutNewRb(AcDb::kDxfRegAppName);
        pTemp = pRb;
        const size_t nSize = _tcslen(appName) + 1;
        pTemp->resval.rstring
            = (TCHAR*) malloc(nSize * sizeof(TCHAR));
        errno_t err = _tcscpy_s(pTemp->resval.rstring, nSize, appName);
        assert(err == 0);
    }

    // Add user-specified string to the xdata.
    //
    pTemp->rbnext = acutNewRb(AcDb::kDxfXdAsciiString);
    pTemp = pTemp->rbnext;
    const size_t nSize = _tcslen(resString) + 1;
    pTemp->resval.rstring
        = (TCHAR*) malloc(nSize * sizeof(TCHAR));
    errno_t err = _tcscpy_s(pTemp->resval.rstring, nSize, resString);
    assert(err == 0);

    // The following code shows the use of upgradeOpen()
    // to change the entity from read to write.
    //
    pObj->upgradeOpen();
    pObj->setXData(pRb);
    
    pObj->close();
    acutRelRb(pRb);
}

// END CODE APPEARING IN SDK DOCUMENT.

// This function accepts a linked list of resbufs as it's
// argument and runs through the list printing out the
// restype and resval values one set per line.
// 
void
printList(struct resbuf* pRb)
{
    int rt, i;
    TCHAR buf[133];

    for (i = 0;pRb != NULL;i++, pRb = pRb->rbnext) {
        if (pRb->restype < 1010) {
            rt = RTSTR;
        } else if (pRb->restype < 1040) {
            rt = RT3DPOINT;
        } else if (pRb->restype < 1060) {
            rt = RTREAL;
        } else if (pRb->restype < 1071) {
            rt = RTSHORT;
        } else if (pRb->restype == 1071) {
            rt = RTLONG;
        } else {// restype is already RTSHORT, RTSTR,...
            rt = pRb->restype; // or else it is unknown.
        }

        switch (rt) {
        case RTSHORT:
            if (pRb->restype == RTSHORT) {
                acutPrintf(
                    _T("RTSHORT : %d\n"), pRb->resval.rint);
            } else {
                acutPrintf(_T("(%d . %d)\n"), pRb->restype,
                    pRb->resval.rint);
            };
            break;

        case RTREAL:
            if (pRb->restype == RTREAL) {
                acutPrintf(_T("RTREAL : %0.3f\n"),
                    pRb->resval.rreal);
            } else {
                acutPrintf(_T("(%d . %0.3f)\n"), pRb->restype,
                    pRb->resval.rreal);
            };
            break;

        case RTSTR:
            if (pRb->restype == RTSTR) {
                acutPrintf(_T("RTSTR : %s\n"),
                    pRb->resval.rstring);
            } else {
                acutPrintf(_T("(%d . \"%s\")\n"), pRb->restype,
                    pRb->resval.rstring);
            };
            break;

        case RT3DPOINT:
            if (pRb->restype == RT3DPOINT) {
                acutPrintf(
                    _T("RT3DPOINT : %0.3f, %0.3f, %0.3f\n"),
                    pRb->resval.rpoint[X],
                    pRb->resval.rpoint[Y],
                    pRb->resval.rpoint[Z]);
            } else {
                acutPrintf(_T("(%d %0.3f %0.3f %0.3f)\n"),
                    pRb->restype,
                    pRb->resval.rpoint[X],
                    pRb->resval.rpoint[Y],
                    pRb->resval.rpoint[Z]);
            }
            break;

        case RTLONG:
            acutPrintf(_T("RTLONG : %dl\n"), pRb->resval.rlong);
            break;
        }

        if ((i == 23) && (pRb->rbnext != NULL)) {
            i = 0;
            acedGetString(0,
                _T("Press <ENTER> to continue..."), buf);
        }
    }
}

// This function prompts the user to select an entity or
// enter an object's handle.  It then proceeds to open the
// object/entity and return a pointer to it.
// 
AcDbObject*
selectObject(AcDb::OpenMode openMode)
{
    // Allow user to either pick an entity, or type in the
    // object handle.
    //
    int ss;
    ads_name en;
    ads_point pt;
    acedInitGet(RSG_OTHER, _T("Handle _Handle"));
    ss = acedEntSel(_T("\nSelect an Entity or enter")
        _T(" 'H' to enter its handle:  "), en, pt);

    TCHAR handleStr[132];
    AcDbObjectId eId;
    switch (ss) {
    case RTNORM:   // got it!
        break;
    case RTKWORD:
        if ((acedGetString(Adesk::kFalse,
            _T("Enter Valid Object Handle: "),
            handleStr) == RTNORM)
            && (acdbHandEnt(handleStr, en) == RTNORM))
        {
            break;
        }
    // Fall-through intentional
    //
    default:
       acutPrintf(_T("Nothing Selected, Return Code==%d\n"),ss);
       return NULL;
    }
    // Now, exchange the ads_name for the object Id...
    //
    Acad::ErrorStatus retStat;
    retStat = acdbGetObjectId(eId, en);
    if (retStat != Acad::eOk) {
        acutPrintf(_T("\nacdbGetObjectId failed"));
        acutPrintf(_T("\nen==(%lx,%lx), retStat==%d\n"),
            en[0], en[1], eId);
        return NULL;
    }

    AcDbObject* obj;

    if ((retStat = acdbOpenObject(obj, eId, openMode))
        != Acad::eOk)
    {
        acutPrintf(_T("acdbOpenEntity failed: ename:(%lx,%lx),")
            _T(" mode:%d retStat:%d"), en[0], en[1],
            openMode, retStat);
        return NULL;
    }
    return obj;
}


// Initialization function called from acrxEntryPoint during
// kInitAppMsg case.  This function is used to add commands
// to the command stack.
// 
void
initApp()
{
    acedRegCmds->addCommand(_T("ASDK_XDATA_DEMO"),
        _T("ASDK_PRINTX"), _T("PRINTX"), ACRX_CMD_MODAL,
        printXdata);
    acedRegCmds->addCommand(_T("ASDK_XDATA_DEMO"),
        _T("ASDK_ADDXDATA"), _T("ADDXDATA"), ACRX_CMD_MODAL,
        addXdata);

}


// Clean up function called from acrxEntryPoint during the
// kUnloadAppMsg case.  This function removes this app's
// command set from the command stack.
// 
void
unloadApp()
{
    acedRegCmds->removeGroup(_T("ASDK_XDATA_DEMO"));
}


// ARX entry point
//
AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* appId)
{
    switch (msg) {
    case AcRx::kInitAppMsg:
        acrxDynamicLinker->unlockApplication(appId);
		acrxDynamicLinker->registerAppMDIAware(appId);
        initApp();
        break;
    case AcRx::kUnloadAppMsg:
        unloadApp();
    }
    return AcRx::kRetOK;
}
