/////////////////////////////////////////////////////////////////////
// (C) Copyright 1993-2005 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.
//
//
/////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "acm.h"
#include "acmdef.h"
#include "bommgr.h"

#include "stdmgr.h"
#include "cauxent.h"
#include "symstd.h"
#include "bomstd.h"
#include "symbol.h"
#include "balloon.h"

Acad::ErrorStatus postToDb(AcDbEntity* ent, AcDbObjectId& objId);
static void dumpStringMap(const CMapStringToString& valueMap, TCHAR *sep);
static AcDbObjectIdArray selectRefArray();
static void dumpItem(const AcDbObjectId& itemId);

void dumpItem(const AcDbObjectId& itemId)
{
  CMapStringToString valueMap;
  CString itemno;
  Adesk::UInt32 number;
  Adesk::Boolean isAutoCalc,isExpanded,isExpandable;

  acutPrintf(_T("\n-------------------------\n"));
  Acad::ErrorStatus es = acmBomMgr->getItemData(itemId,itemno,number,valueMap);
  if( es != Acad::eOk ) return;
  acutPrintf(_T("Item Number: '%s', Number of items :%d\n"),itemno,number);
  dumpStringMap(valueMap,_T(", "));
  es = acmBomMgr->getItemStatus(itemId,isAutoCalc,isExpanded,isExpandable);
  if( es != Acad::eOk ) return;
  acutPrintf(_T("\nAutoCalc : %s, Expanded : %s, Expandable :%s\n"),(isAutoCalc ? _T("Yes") : _T("No")),
    (isExpanded ? _T("Yes") : _T("No")),(isExpandable ? _T("Yes") : _T("No")));
    
}

void dumpStringMap(const CMapStringToString& valueMap, TCHAR *sep)
{
  POSITION pos = valueMap.GetStartPosition();
  while( pos != NULL ) {
    CString value;
    CString key;
    // Gets key (string) and value (pPerson)
    valueMap.GetNextAssoc( pos, key,value );
    acutPrintf(_T("%s : '%s'%s"),key,value,sep);
  }
}

bool handleAttributeKey(const ACHAR* pKey, CString& attrib)
{
  CString prompt;   
  if (!attrib.IsEmpty())
    prompt.Format(_T("\nSet value for %s <%s> :"),pKey,attrib);
  else
    prompt.Format(_T("\nSet value for %s :"),pKey);

  if (_tcsicmp(pKey,_T(/*MSG0*/"BOM_UNITS")) == 0) {
    AcDbObjectId stdId;
    if (acmStdMgr->getCurrent(stdId) != Acm::eOk)
      return Adesk::kFalse;

    AcmStandard* pStandard;
    if (acdbOpenObject(pStandard, stdId, AcDb::kForRead))
      return Adesk::kFalse;

    // get BOM Standard
    AcDbObjectId bomStdId;
    if(pStandard->getCurrentSymbolStd(_T("AcmBOMStd"), bomStdId) != Acad::eOk){
      pStandard->close();
      return Adesk::kFalse;
    }
    pStandard->close();

    AcmBOMStd* pBomStd; 
    if (acdbOpenObject(pBomStd, bomStdId, AcDb::kForRead))
      return Adesk::kFalse;     

    CString fileTypeKeyword;
    prompt += _T(" Each/sqm/sqcm/sqft/sqin/gal/qt/lqdoz/l/ml/yd/ft/in/m/cm/mm/lb/oz/g/mg/bx :");
    acedInitGet(0, _T("Each sqm sqcm sqft sqin gal qt lqdoz l ml yd ft in m cm mm lb oz g mg bx _ Each sqm sqcm sqft sqin gal qt lqdoz l ml yd ft in m cm mm lb oz g mg bx"));
    int res = acedGetKword(prompt, fileTypeKeyword.GetBuffer(150));
    if( res == RTNONE || res == RTCAN || res == RTERROR ) {
      pBomStd->close(); 
      return Adesk::kFalse;
    }

    Acm::UOMType type = Acm::Each;
    if (fileTypeKeyword == _T("Each")) type = Acm::Each;
    else if (fileTypeKeyword == _T("sqm")) type = Acm::sqm;     
    else if (fileTypeKeyword == _T("sqcm")) type = Acm::sqcm;
    else if (fileTypeKeyword == _T("sqft")) type = Acm::sqft;
    else if (fileTypeKeyword == _T("sqin")) type = Acm::sqin;
    else if (fileTypeKeyword == _T("gal")) type = Acm::gal;
    else if (fileTypeKeyword == _T("qt")) type = Acm::qt;
    else if (fileTypeKeyword == _T("lqd oz")) type = Acm::lqd_oz;
    else if (fileTypeKeyword == _T("l")) type = Acm::l;
    else if (fileTypeKeyword == _T("ml")) type = Acm::ml;
    else if (fileTypeKeyword == _T("yd")) type = Acm::yd;
    else if (fileTypeKeyword == _T("ft")) type = Acm::ft;
    else if (fileTypeKeyword == _T("in")) type = Acm::in;
    else if (fileTypeKeyword == _T("m")) type = Acm::m;
    else if (fileTypeKeyword == _T("cm")) type = Acm::cm;
    else if (fileTypeKeyword == _T("mm")) type = Acm::mm;
    else if (fileTypeKeyword == _T("lb")) type = Acm::lb;
    else if (fileTypeKeyword == _T("oz")) type = Acm::oz;
    else if (fileTypeKeyword == _T("g")) type = Acm::g;
    else if (fileTypeKeyword == _T("mg")) type = Acm::mg;
    else if (fileTypeKeyword == _T("bx")) type = Acm::bx;

    pBomStd->getUOMName(type, attrib);

    pBomStd->close(); 
    return Adesk::kTrue;
  }

  // other attribute key
  acedInitGet(RSG_NONULL,_T(""));     
  if( acedGetString(1,prompt,attrib.GetBuffer(150)) == RTCAN) return Adesk::kFalse;
  if( attrib == _T(".") ) attrib = _T("");     
  attrib.ReleaseBuffer();
  return Adesk::kTrue;
}

Adesk::Boolean 
editStringMap(const CMapStringToString& valueMap,
              CMapStringToString& newValueMap)
{
  POSITION pos = valueMap.GetStartPosition();
  CString value, key;
  while( pos != NULL ) {
    // Gets key (string) and value (pPerson)
    valueMap.GetNextAssoc( pos, key,value );
    if (!handleAttributeKey(key,value))
      continue;    
    newValueMap.SetAt(key,value);
  }
  return Adesk::kTrue;
}


AcDbObjectIdArray selectRefArray()
{                    
  AcDbObjectIdArray resultArray;
  ads_name entres;
  ads_point ptres, xformres[4];
  struct resbuf *rb,*refstkres = NULL;
  // acedNEntSel returns selected entity. Even if the entity is a part of a block ref., this
  // call returns the entity and not the block ref. Block references that the entity is part of
  // are returned in refstkres
  if( acedNEntSel(_T("\nSelect Part Reference:"), entres,ptres,xformres,&refstkres) != RTNORM) 
    return resultArray;

  // check selected entity type. If it is AcmPartRef, than we'll use its Id
  AcDbObjectId refId;
  AcDbObject *pObj;
  if( acdbGetObjectId(refId,entres) != Acad::eOk ) return resultArray;
  if( acdbOpenObject(pObj,refId,AcDb::kForRead) != Acad::eOk) return resultArray;
  CString cname = pObj->isA()->name();
  if( cname == _T("AcmPartRef")) resultArray.append(refId);
  pObj->close();

  // now add any block reference found in refstkres.
  rb = refstkres;
  while( rb ) {
    if( rb->restype == RTENAME && 
        acdbGetObjectId(refId,rb->resval.rlname) == Acad::eOk
      ) {
      resultArray.insertAt(0,refId);
    }
    rb = rb->rbnext;
  }
  if( refstkres ) acutRelRb(refstkres);

  // now in resultArray the selected entity is the last one member, and the previous members
  // (if any) are the block references that the entity is a part of, sorted from the most
  // upper (the one that is really inserted into the drawing) as the first member in array.
  return resultArray;
}

Adesk::Boolean selectBTRId(AcDbObjectId& BTRId)
{
  ads_name entres;
  ads_point ptres;
  int res = acedEntSel(_T("\nSelect subassembly Reference <enter for main>:"), entres,ptres);

  if( res == RTNORM ) {
    AcDbObjectId refId;
    if( acdbGetObjectId(refId,entres) != Acad::eOk ) return Adesk::kFalse;
    AcDbBlockReference * blkRef;
    if( acdbOpenObject(blkRef,refId,AcDb::kForRead) != Acad::eOk) return Adesk::kFalse;
    BTRId = blkRef->blockTableRecord();
    blkRef->close();
  }
  return Adesk::kTrue;
}

Adesk::Boolean selectBorderId(AcDbObjectId& borderId)
{
  ads_name entres;
  ads_point ptres;
  int res = acedEntSel(_T("\nSelect border:"), entres,ptres);

  if( res == RTNORM ) {
    AcDbObjectId refId;
    if( acdbGetObjectId(refId,entres) != Acad::eOk ) return Adesk::kFalse;    
    borderId = refId;    
  }
  return Adesk::kTrue;
}

Adesk::Boolean getYesOrNo(LPCTSTR str)
{
  CString tmp, prompt = str;
  prompt += _T(" Yes/<No> :");
  acedInitGet(0, _T("Yes No _ Yes No"));
  int res = acedGetKword(prompt,tmp.GetBuffer(150));
  if( res == RTNONE || res == RTCAN || res == RTERROR ) return Adesk::kFalse;
  return ( tmp == _T("Yes") ? Adesk::kTrue : Adesk::kFalse );

}

// Get file type of BOM exported file
Adesk::Boolean getExportFileType(const ACHAR* str, AcmBOMManager::ExportBOMType& exportFileType)
{
  CString fileTypeKeyword, prompt = str;
  prompt += _T(" MDB/MDB97/EXCEL97/EXCEL95/DB5/DBIV/DBIII/FOXVIS/FOX26/FOX25/TXT/CSV/HTML :");
  acedInitGet(0, _T("MDB MDB97 EXCEL97 EXCEL95 DB5 DBIV DBIII FOXVIS FOX26 FOX25 TXT CSV HTML _ MDB MDB97 EXCEL97 EXCEL95 DB5 DBIV DBIII FOXVIS FOX26 FOX25 TXT CSV HTML"));
  int res = acedGetKword(prompt, fileTypeKeyword.GetBuffer(150));
  if( res == RTNONE || res == RTCAN || res == RTERROR ) return Adesk::kFalse;

  if (fileTypeKeyword == _T("MDB"))
      exportFileType = AcmBOMManager::eEXPORT_MDB;
    else if (fileTypeKeyword == _T("MDB97"))
      exportFileType = AcmBOMManager::eEXPORT_MDB97;
    else if (fileTypeKeyword == _T("EXCEL97"))
      exportFileType = AcmBOMManager::eEXPORT_EXCEL97;
    else if (fileTypeKeyword == _T("EXCEL95"))
      exportFileType = AcmBOMManager::eEXPORT_EXCEL95;
    else if (fileTypeKeyword == _T("DB5"))
      exportFileType = AcmBOMManager::eEXPORT_DB5;
    else if (fileTypeKeyword == _T("DBIV"))
      exportFileType = AcmBOMManager::eEXPORT_DBIV;
    else if (fileTypeKeyword == _T("DBIII"))
      exportFileType = AcmBOMManager::eEXPORT_DBIII;
    else if (fileTypeKeyword == _T("FOXVIS"))
      exportFileType = AcmBOMManager::eEXPORT_FOXVIS;
    else if (fileTypeKeyword == _T("FOX26"))
      exportFileType = AcmBOMManager::eEXPORT_FOX26;
    else if (fileTypeKeyword == _T("FOX25"))
      exportFileType = AcmBOMManager::eEXPORT_FOX25;
    else if (fileTypeKeyword == _T("TXT"))
      exportFileType = AcmBOMManager::eEXPORT_TXT;
    else if (fileTypeKeyword == _T("CSV"))
      exportFileType = AcmBOMManager::eEXPORT_CSV;
    else if (fileTypeKeyword == _T("HTML"))
      exportFileType = AcmBOMManager::eEXPORT_HTML;

  return Adesk::kTrue;
}

// Get file type of BOM imported file
Adesk::Boolean getImportFileType(const ACHAR* str, AcmBOMManager::ImportBOMType& importFileType)
{
  CString fileTypeKeyword, prompt = str;
  prompt += _T(" MDB/EXCEL97/EXCEL95/DB5/DBIV/DBIII/FOXVIS/FOX26/FOX25/TXT/CSV :");
  acedInitGet(0, _T("MDB EXCEL97 EXCEL95 DB5 DBIV DBIII FOXVIS FOX26 FOX25 TXT CSV  _ MDB EXCEL97 EXCEL95 DB5 DBIV DBIII FOXVIS FOX26 FOX25 TXT CSV"));
  int res = acedGetKword(prompt, fileTypeKeyword.GetBuffer(150));
  if( res == RTNONE || res == RTCAN || res == RTERROR ) return Adesk::kFalse;

  if (fileTypeKeyword == _T("MDB"))
    importFileType = AcmBOMManager::eIMPORT_MDB;  
  else if (fileTypeKeyword == _T("EXCEL97"))
    importFileType = AcmBOMManager::eIMPORT_EXCEL97;
  else if (fileTypeKeyword == _T("EXCEL95"))
    importFileType = AcmBOMManager::eIMPORT_EXCEL95;
  else if (fileTypeKeyword == _T("DB5"))
    importFileType = AcmBOMManager::eIMPORT_DB5;
  else if (fileTypeKeyword == _T("DBIV"))
    importFileType = AcmBOMManager::eIMPORT_DBIV;
  else if (fileTypeKeyword == _T("DBIII"))
    importFileType = AcmBOMManager::eIMPORT_DBIII;
  else if (fileTypeKeyword == _T("FOXVIS"))
    importFileType = AcmBOMManager::eIMPORT_FOXVIS;
  else if (fileTypeKeyword == _T("FOX26"))
    importFileType = AcmBOMManager::eIMPORT_FOX26;
  else if (fileTypeKeyword == _T("FOX25"))
    importFileType = AcmBOMManager::eIMPORT_FOX25;
  else if (fileTypeKeyword == _T("TXT"))
    importFileType = AcmBOMManager::eIMPORT_TXT;
  else if (fileTypeKeyword == _T("CSV"))
    importFileType = AcmBOMManager::eIMPORT_CSV;  

  return Adesk::kTrue;
}

/////////////////////////////////////////////////////////////////////////////////
// Commands
/////////////////////////////////////////////////////////////////////////////////

// throughout the comments in this file, 'object' means either part reference (when
// a method gets the object through selectRefArray() call, or a record (BomRow) 
// in BOM table (when given by a number and a BOM table).

// prints out data for selected object
//
void getPartData()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  Adesk::UInt32 numIt;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getPartData(refArray.last(),numIt,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part data (%d).\n"),es);
    return;
  }
  acutPrintf(_T("\nBOM Related data\n"));
  acutPrintf(_T("Number of Items for reference is : %d\n"),numIt);
  dumpStringMap(valueMap,_T("\n"));
}

// sets data for selected object
//
void setPartData()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  Adesk::UInt32 numIt;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getPartData(refArray.last(),numIt,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part data (%d).\n"),es);
    return;
  }

  int tmpNumIt;
  CString prompt, tmp;
  prompt.Format(_T("\nNumber of Items for reference <%d> :"),numIt);
  acedInitGet(RSG_NONEG,_T(""));
  int res = acedGetInt(prompt,&tmpNumIt);
  if( res == RTCAN ) return;
  if( res == RTNORM ) numIt = tmpNumIt;

  CMapStringToString newValueMap;
  if( !editStringMap(valueMap,newValueMap)) return;

  es = acmBomMgr->setPartData(refArray.last(),numIt,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for set part data (%d).\n"),es);
    return;
  }
}

// prints out data of all objects
//
void allPartData()
{
  AcmIterator *pIt = acmBomMgr->newPartDataIterator();
  if (pIt  == NULL ) return;
  if (pIt->numEntries() == 0) {
    delete pIt;
    return;
  }

  Acad::ErrorStatus es;
  CMapStringToString valueMap;
  int i;
  for(i=1 ; !pIt->done(); pIt->next()) {
    AcDbObjectId dataId = pIt->objectId();
    es = pIt->getData(valueMap);
    if( es == Acad::eOk ) {
      acutPrintf(_T("\n\nEntry number %d.\n"),i++);
      dumpStringMap(valueMap,_T("\n"));
    }
  }
  delete pIt;
}

// creates BOM table (either main or for selected subassembly only)
//
void createBomTable()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  acedInitGet(RSG_NONULL,_T(""));
  if( acedGetString(1,_T("\nEnter BOM Table name :"),name.GetBuffer(150)) == RTCAN) return;
  name.ReleaseBuffer();
  
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->createBomTable(bomId,BTRId,name);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating bom table (%d).\n"), es);
    return;
  }

  // to get all data available
  CMapStringToString valueMap;
  es = acmBomMgr->getBomData(bomId,name,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom data (%d).\n"), es);
    return;
  }

  CMapStringToString newValueMap;
  if( !editStringMap(valueMap,newValueMap)) return;

  es = acmBomMgr->setBomData(bomId,name,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for seting bom data (%d).\n"), es);
    return;
  }

}

// creates Border BOM table
//
void createBorderBomTable()
{
  AcDbObjectId borderId;
  if( !selectBorderId(borderId)) return;

  CString name;
  acedInitGet(RSG_NONULL,_T(""));
  if( acedGetString(1,_T("\nEnter BOM Table name :"),name.GetBuffer(150)) == RTCAN) return;
  name.ReleaseBuffer();

  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->createBorderBomTable(bomId,borderId,name,true);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating bom table (%d).\n"), es);
    return;
  }  
}

// returns BOM table for selected subassembly or main BOM table
//
void getBomTable()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);

  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError - selected block doesn't have BOM table associated.\n"));
    return;
  }
  acutPrintf(_T("\nBom name : %s\n"), name);

  CMapStringToString valueMap;
  es = acmBomMgr->getBomData(bomId,name,valueMap);
  if( es == Acad::eOk ) dumpStringMap(valueMap,_T("\n"));
  
  CString kwd = _T("Yes");
  acedInitGet(0,_T("No Yes"));
  int res = acedGetKword(_T("\nList all items data ? No/<Yes> :"), kwd.GetBuffer(150));
  kwd.ReleaseBuffer();
  if( kwd.IsEmpty()) kwd = _T("Yes");
  if( (res == RTNORM && kwd == _T("No")) || (res != RTNORM && res != RTNONE) ) 
    return;

  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  if (pIt  == NULL ) { 
    acutPrintf(_T("\nNo item in table."));
    return;
  }

  int i;

  for(i=1 ; !pIt->done(); pIt->next()) {
    AcDbObjectId itemId = pIt->objectId();
    dumpItem(itemId);
  }
  delete pIt;
}

// changes selected BOM table's data (name, ...)
//
void setBomTableData()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // to get all data available
  CMapStringToString valueMap;
  es = acmBomMgr->getBomData(bomId,name,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom data (%d).\n"), es);
    return;
  }

  CString prompt;
  CString tmp;
  prompt.Format(_T("\nSet new BOM table name <%s> :"), name);
  if( acedGetString(1,prompt,tmp.GetBuffer(150)) == RTCAN) return;
  tmp.ReleaseBuffer();
  if( tmp == _T(".") ) name = _T("");
  if( tmp != _T("") ) 
    name = tmp;

  CMapStringToString newValueMap;
  if( !editStringMap(valueMap,newValueMap)) return;

  es = acmBomMgr->setBomData(bomId,name,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for seting bom data (%d).\n"), es);
    return;
  }
}

// removes selected BOM table from the drawing
//
void eraseBomTable()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  CString prompt;
  prompt.Format(_T("\nDo you realy want to erase BOM table '%s' ?"),name);
  if( !getYesOrNo(prompt)) return;

  AcDbObject *pObj;
  es = acdbOpenObject(pObj,bomId,AcDb::kForWrite);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for opening bom table (%d).\n"), es);
    return;
  }
  pObj->erase();
  pObj->close();

}

// creates new item (BomRow) in current BOM Table
// and appends reference to the object that user has selected previously
// (compare with addRef(), which only appends reference to an item that already exists)
//
void createItem()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem number :"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  AcDbObjectId itemId;
  Acad::ErrorStatus es = acmBomMgr->createItem(itemId,bomId,refArray,itNo);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating item (%d).\n"),es);
    return;
  }
  dumpItem(itemId);
}

// creates new item (BomRow) in current BOM table.
// This item will have no reference to an object in drawing appended.
//
void createItemUnassoc()
{
  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nUnassociated Item Number :"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  int num;
  acedInitGet(RSG_NONEG,_T(""));
  res = acedGetInt(_T("\nNumber of items for unassociated item :"),&num);
  if( res == RTCAN || res == RTERROR ) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  CMapStringToString valueMap;
  AcDbObjectId itemId;
  Acad::ErrorStatus es = acmBomMgr->createItem(itemId, bomId, itNo,(Adesk::UInt16)num, valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating item (%d).\n"),es);
    return;
  }
  Adesk::UInt32 numItems;
  es = acmBomMgr->getItemData(itemId,itNo,numItems, valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting default item data(%d).\n"),es);
    return;
  }
  CMapStringToString newValueMap;
  if( !editStringMap(valueMap,newValueMap)) return;
  es = acmBomMgr->setItemData(itemId,itNo,numItems,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for seting item data(%d).\n"),es);
    return;
  }

}

// prints out data for object with specified number in user-selected BOM table
//
void findItemItemNo()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number :"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  Adesk::UInt32 numItem;
  CMapStringToString valueMap;
  for( ; !pIt->done(); pIt->next()) {
    es = acmBomMgr->getItemData(pIt->objectId(),itemNo,numItem,valueMap);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item data (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      dumpItem(pIt->objectId());
      return;
    }
  }
  acutPrintf(_T("\nError - specified item number not found !"));
}

// prints out data for user-selected object
//
void getItemRef()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  AcDbObjectId itemId;
  CString itemNo;
  Adesk::UInt32 numItem;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getItemData(refArray.last(),bomId,itemId,itemNo,numItem,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting item data item (%d).\n"),es);
    return;
  }
  dumpItem(itemId);
}

// sets data for object specified by number in user-selected BOM table
//
void setItemNo()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number :"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;
  Adesk::UInt32 numItem;
  CMapStringToString valueMap;
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemData(itemId,itemNo,numItem,valueMap);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item data (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  delete pIt;
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }

  CString tmp, prompt;
  prompt.Format(_T("\nItem Number <%s>:"), itemNo);
  res = acedGetString(0,prompt,tmp.GetBuffer(150));
  tmp.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;
  if( tmp != _T("") ) itemNo = tmp;

  int num;
  acedInitGet(RSG_NONEG,_T(""));
  prompt.Format(_T("\nNumber of items <%d> :"),numItem);
  res = acedGetInt(prompt,&num);
  if( res == RTCAN || res == RTERROR ) return;
  if( res == RTNORM ) numItem = num;

  CMapStringToString newValueMap;
  if( !editStringMap(valueMap, newValueMap)) return;
  es = acmBomMgr->setItemData(itemId,itemNo,numItem,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for seting item data (%d).\n"),es);
    return;
  }
}

// sets data for user-selected object
//
void setItemRef()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  AcDbObjectId itemId;
  CString itemNo;
  Adesk::UInt32 numItem;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getItemData(refArray.last(),bomId,itemId,itemNo,numItem,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating item (%d).\n"),es);
    return;
  }
  
  Adesk::Boolean isAutoCalc,isExpanded,isExpandable;
  es = acmBomMgr->getItemStatus(itemId,isAutoCalc,isExpanded,isExpandable);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating item (%d).\n"),es);
    return;
  }

  CString tmp, prompt;
  prompt.Format(_T("\nItem Number <%s>:"), itemNo);
  int res = acedGetString(0,prompt,tmp.GetBuffer(150));
  tmp.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;
  if( tmp != _T("") ) itemNo = tmp;

  if (!isAutoCalc){
    int num;
    acedInitGet(RSG_NONEG,_T(""));
    prompt.Format(_T("\nNumber of items <%d> :"),numItem);
    res = acedGetInt(prompt,&num);
    if( res == RTCAN || res == RTERROR ) return;
    if( res == RTNORM ) numItem = num;
  }

  CMapStringToString newValueMap;
  if( !editStringMap(valueMap, newValueMap)) return;
  es = acmBomMgr->setItemData(itemId,itemNo,numItem,newValueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for seting item data (%d).\n"),es);
    return;
  }
}

// erases item given by number from user-selected BOM table
//
void eraseItemItemNo()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number :"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  Adesk::UInt32 numItem;
  AcDbObjectId itemId;
  CMapStringToString valueMap;
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId = pIt->objectId();
    es = acmBomMgr->getItemData(itemId,itemNo,numItem,valueMap);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item data (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }

  CString prompt;
  prompt.Format(_T("\nDo you realy want to erase Item Number '%s' ?"),itemNo);
  if( !getYesOrNo(prompt)) return;
  
  AcDbObject *pObj;
  es = acdbOpenObject(pObj,itemId, AcDb::kForWrite);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for opening item (%d).\n"),es);
    return;
  }
  pObj->erase();
  pObj->close();
}

// erases user-selected item from current BOM table
//
void eraseItemRef()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  AcDbObjectId itemId;
  CString itemNo;
  Adesk::UInt32 numItem;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getItemData(refArray.last(),bomId,itemId,itemNo,numItem,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating item (%d).\n"),es);
    return;
  }

  CString prompt;
  prompt.Format(_T("\nDo you realy want to erase Item Number '%s' ?"),itemNo);
  if( !getYesOrNo(prompt)) return;
  
  AcDbObject *pObj;
  es = acdbOpenObject(pObj,itemId, AcDb::kForWrite);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for opening item (%d).\n"),es);
    return;
  }
  pObj->erase();
  pObj->close();
}                          

// adds refence to the selected object to the item (BomRow) in current bom that
// is associated with this object
//
void addRef()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  CString itNo;
  CMapStringToString valueMap;
  Adesk::UInt32 num;
  AcDbObjectId itemId;
  Acad::ErrorStatus es = acmBomMgr->getItemData(refArray.last(),bomId,itemId,itNo,num,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting item data (%d).\n"),es);
    return;
  }
  es = acmBomMgr->addItemReference(itemId,refArray);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for adding reference (%d).\n"),es);
    return;
  }

  dumpItem(itemId);
}

// removes refence to an object from its associated BOM row
//
void removeRef()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  //ask for current bom id 
  AcDbObjectId bomId;
  if (acmBomMgr->getCurrentBom(bomId) != Acad::eOk)
    return;

  CString itNo;
  CMapStringToString valueMap;
  Adesk::UInt32 num;
  AcDbObjectId itemId;
  Acad::ErrorStatus es = acmBomMgr->getItemData(refArray.last(),bomId,itemId,itNo,num,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting item data (%d).\n"),es);
    return;
  }
  es = acmBomMgr->removeItemReference(itemId,refArray);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for removing reference (%d).\n"),es);
    return;
  }
  dumpItem(itemId);
}

// adds new graphical representation to selected partref -
// - so that the partref can have more than one graphics associated to it,
// like upper and side views of the partref
//
void addGrRep()
{
  // select partref to which add new graphical representation
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  Adesk::UInt32 numIt;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getPartData(refArray.last(),numIt,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part data (%d).\n"),es);
    return;
  }
  acutPrintf(_T("\nBOM Related data\n"));
  acutPrintf(_T("Number of Items for reference is : %d\n"),numIt);
  dumpStringMap(valueMap,_T("\n"));

  // select new graphical representation
  // this graphics will be removed from the representations of its associated partref, if any.
  ads_name entres;
  ads_point ptres;
  acedInitGet(RSG_NONULL, _T(""));
  int res = acedEntSel(_T("\nSelect block reference for new graphical definition:"), entres,ptres);
  if( res != RTNORM ) return;

  AcDbObjectId refId;
  if( acdbGetObjectId(refId,entres) != Acad::eOk ) return;
  AcDbBlockReference * blkRef;
  if( acdbOpenObject(blkRef,refId,AcDb::kForRead) != Acad::eOk) return;
  AcDbObjectId BTRId = blkRef->blockTableRecord();
  blkRef->close();

  es = acmBomMgr->addPartGrfDef(refArray.last(),BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for adding graph. definition (%d).\n"),es);
    return;
  }

}

// removes graphical representation from partref
// the block reference will no longer be associated with any partref, even if it was previously
// associated with another partref and than "reassociated" by a call to addGrRep() method.
//
void removeGrRep()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  Adesk::UInt32 numIt;
  CMapStringToString valueMap;
  Acad::ErrorStatus es = acmBomMgr->getPartData(refArray.last(),numIt,valueMap);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part data (%d).\n"),es);
    return;
  }
  acutPrintf(_T("\nBOM Related data\n"));
  acutPrintf(_T("Number of Items for reference is : %d\n"),numIt);
  dumpStringMap(valueMap,_T("\n"));

  if( !getYesOrNo(_T("\nDo you realy want to remove Graphical representation ?"))) return;
  
  es = acmBomMgr->removePartGrfDef(refArray.last());
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for removing graph. definition (%d).\n"),es);
    return;
  }

}

// creates new part data (only for object that does not have any part data yet)
//
void createPartData()
{
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  CMapStringToString valueMap, newValueMap;
   
  AcDbObjectId stdId;
  if (acmStdMgr->getCurrent(stdId) != Acm::eOk)
    return;
    
  AcmStandard* pStandard;
  if (acdbOpenObject(pStandard, stdId, AcDb::kForRead))
    return;
  
  // get BOM Standard
  AcDbObjectId bomStdId;
  if(pStandard->getCurrentSymbolStd(_T("AcmBOMStd"), bomStdId) != Acad::eOk){
    pStandard->close();
    return;
  }
  pStandard->close();

  AcmBOMStd* pBomStd; 
  if (acdbOpenObject(pBomStd, bomStdId, AcDb::kForRead))
    return;

  CString numItemColumnName=_T("");
  Adesk::Int16 numOfItems = 1;
  Adesk::Int16 len = pBomStd->columnLength();
  for (int register i=0; i<len; i++){
    const AcmCColumnDef* pColumn = ((const AcmBOMStd*)pBomStd)->column(i);
    switch (pColumn->type()){
    case Acm::kItem :
      // Item number is saved in BOM row 
      break;
    case Acm::kQuantity :
      // number of Items  
      numItemColumnName = pColumn->name();
      valueMap.SetAt(pColumn->name(), _T("1"));
      break;
    default :
      valueMap.SetAt(pColumn->name(), _T(""));
      break;
    }
  }
  pBomStd->close();

  if (!editStringMap(valueMap, newValueMap))
    return;

  CString val;
  if (newValueMap.Lookup(numItemColumnName, val)){
    numOfItems = _ttoi(val);
  }
  
  Acad::ErrorStatus es = acmBomMgr->createPartData(refArray.last(),newValueMap);
  if (es == Acad::eAlreadyInDb){
    acutPrintf(_T("\nThe Part Reference already have the part data."));
    return;
  }
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for set part data (%d).\n"),es);
    return;
  }
}

/////////////////////////////////////////////////////////////////////////////////
// Functions for new APIs added in ACM2007
/////////////////////////////////////////////////////////////////////////////////

// Export BOM table to a specific format
// The column formatting is based on the current BOM Standard 
// In order to use other BOM Standard, call the API to switch to that BOM Standard first
//
void exportBOM()
{
  AcDbObjectId BTRId;
  if (!selectBTRId(BTRId)) return;

  // Get BOM Table
  CString bomTableName;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId, bomTableName, BTRId);
  if (Acad::eOk != es) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // Get full path and filename of exported file
  CString fileName;
  acedInitGet(RSG_NONULL,_T(""));
  if (acedGetString(1,_T("\nEnter full path and filename of exported file: "), fileName.GetBuffer(150)) == RTCAN) 
    return;
  fileName.ReleaseBuffer();

  // Get file type of exported file
  AcmBOMManager::ExportBOMType exportFileType = AcmBOMManager::eEXPORT_MDB;
  CString prompt;
  prompt.Format(_T("\nEnter filetype of exported file for '%s' ?"), bomTableName);
  if (!getExportFileType(prompt, exportFileType)) 
    return;

  // Export BOM table to the specified file
  es = acmBomMgr->exportBOM(exportFileType, bomId, fileName, bomTableName, true);
  if (Acad::eOk != es) {
    acutPrintf(_T("\nError code returned for exporting BOM (%d).\n"), es);
    return;
  }
}

// Import a file of a specific format into a BOM table
//
void importBOM()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  CString name;
  acedInitGet(RSG_NONULL,_T(""));
  if( acedGetString(1,_T("\nEnter BOM Table name :"),name.GetBuffer(150)) == RTCAN) return;
  name.ReleaseBuffer();

  // Create BOM table 
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->createBomTable(bomId,BTRId,name);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for creating bom table (%d).\n"), es);
    return;
  }

  // Get full path and filename of imported file
  CString fileName;
  acedInitGet(RSG_NONULL,_T(""));
  if (acedGetString(1,_T("\nEnter full path and filename of imported file: "), fileName.GetBuffer(150)) == RTCAN) 
    return;
  fileName.ReleaseBuffer();

  // Get file type of imported file
  AcmBOMManager::ImportBOMType importFileType = AcmBOMManager::eIMPORT_MDB;
  CString prompt;
  prompt.Format(_T("\nEnter filetype of imported file for '%s' ?"), name);
  if (!getImportFileType(prompt, importFileType)) 
    return;  
    
  // Specify table name for MDB or EXCEL file
  CString table;
  if (importFileType == AcmBOMManager::eIMPORT_MDB ||
      importFileType == AcmBOMManager::eIMPORT_EXCEL97 ||
      importFileType == AcmBOMManager::eIMPORT_EXCEL95) {      
    acedInitGet(RSG_NONULL,_T(""));
    if( acedGetString(1,_T("\nEnter table name in imported file :"),table.GetBuffer(150)) == RTCAN) return;
    table.ReleaseBuffer();
  }

  // Import BOM from the specified file
  es = acmBomMgr->importBOM(importFileType, bomId, fileName, table, true);
  if (Acad::eOk != es) {
    acutPrintf(_T("\nError code returned for exporting BOM (%d).\n"), es);
    return;
  }
}

// Set the value for a specified BOM attribute for the part reference
//
void setPartAttribute()
{
  // Select part reference
  AcDbObjectIdArray refArray = selectRefArray();
  if( !refArray.length()) return;

  // Specify BOM attribute key
  CString key;  
  acedInitGet(RSG_NONULL,_T(""));
  if( acedGetString(1,_T("\nEnter the attribute key to modify :"),key.GetBuffer(150)) == RTCAN) return;
  key.ReleaseBuffer();    

  // Get BOM attribute for the specified key
  CString attrib;
  Acad::ErrorStatus es = acmBomMgr->getPartAttribute(refArray.last(),key,attrib);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part attribute (%d).\n"),es);
    return;
  }  
  
  // Specify the value for the BOM attribute key
  if (!handleAttributeKey(key, attrib))
    return;  

  // Set value of the BOM attribute for the selected part referemce
  es = acmBomMgr->setPartAttribute(refArray.last(),key,attrib);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for set part attribute (%d).\n"),es);
    return;
  }
}

// Set the value of a specified BOM attribute value for a BOM row item
//
void setItemAttribute()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  // Get BOM Table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // Specify BOM row item no
  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number of bom item to modify:"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  // Get BOM row item for the specified item no
  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;  
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemAttribute(itemId,_T("ITEM"),itemNo);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item attribute (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  delete pIt;
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }  

  // Specify BOM attribute key
  CString key;  
  acedInitGet(RSG_NONULL,_T(""));
  if( acedGetString(1,_T("\nEnter the attribute key to modify :"),key.GetBuffer(150)) == RTCAN) return;
  key.ReleaseBuffer(); 

  // Get BOM attribute for the specified key
  CString attrib;
  es = acmBomMgr->getItemAttribute(itemId,key,attrib);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get part attribute (%d).\n"),es);
    return;
  }  

  // Specify the value for the BOM attribute key  
  if (!handleAttributeKey(key,attrib))
    return;
  
  // Set value of the BOM attribute for the selected BOM row item
  es = acmBomMgr->setItemAttribute(itemId,key,attrib);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for setting item attribute (%d).\n"),es);
    return;
  }
}

// Merge given BOM row items
//
void mergeItems()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  // Get BOM table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }  

  // Get all BOM row items
  AcDbObjectIdArray itemIds;
  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;  
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemIds.append(pIt->objectId());    
  }
  delete pIt;    

  // Merge BOM row items
  es = acmBomMgr->mergeItems(itemIds);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for merging bom items (%d).\n"), es);
    return;
  }
}

// Get the list of balloons associated to the item (and references) in BOM row item
//
void getItemBalloons()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  // Get BOM table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // Specify BOM row item no
  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number of bom item to get balloons:"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;
 
  // Get BOM row item for the specified item no
  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;  
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemAttribute(itemId,_T("ITEM"),itemNo);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item attribute (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  delete pIt;
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }

  // Get all ballons associated with this bom item
  AcDbObjectIdArray balloonIds;
  AcDbObjectId refId = AcDbObjectId::kNull; 
  es = acmBomMgr->getItemBalloons(itemId, balloonIds, refId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting item balloons (%d).\n"), es);
    return;
  }

  // Set balloons for the specified BOM row item to red color
  AcmBalloon* pBall = NULL; 
  for (int i=0; i<balloonIds.length(); i++) {
    if (acdbOpenObject(pBall, balloonIds[i], AcDb::kForWrite))
      return;
    AcCmColor color;
    color.setRGB(255, 0, 0); // red color
    pBall->setColor(color);
    pBall->close();
  }  
}

// Return the part definition type of a specific bom row item
//
void getItemDefinition()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;
  
  // Get BOM table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // Specify BOM row item no
  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number of bom item to get definition:"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  // Get BOM row item for the specified item no
  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;  
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemAttribute(itemId,_T("ITEM"),itemNo);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item attribute (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  delete pIt;
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }

  // Get the component or part definition of the specified BOM row item
  AcmBOMManager::ItemType type;
  AcDbObjectIdArray defIds;
  es = acmBomMgr->getItemDefinition(itemId,defIds,type);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for get item definition (%d).\n"),es);
    return;
  }

  // List the BOM related data of the component or part definition
  Adesk::UInt32 numIt;
  CMapStringToString valueMap;  
  if (type == AcmBOMManager::eCompDef || type == AcmBOMManager::ePartRef || type == AcmBOMManager::eBlockDef) {
    es = acmBomMgr->getPartData(defIds.last(),numIt,valueMap);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for get part data (%d).\n"),es);
      return;
    }
  }
  else if (type == AcmBOMManager::eNonModal) {
    CString itemNo;
    es = acmBomMgr->getItemData(defIds.last(),itemNo,numIt,valueMap);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for get item data (%d).\n"),es);
      return;
    }
  }
  acutPrintf(_T("\nBOM Related data\n"));
  acutPrintf(_T("Number of Items for reference is : %d\n"),numIt);
  dumpStringMap(valueMap,_T("\n"));
}

// Get the boolean value of a specific data associated with a specific bom item
//
void getItemExtendedData()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  // Get BOM table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;    
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemAttribute(itemId,_T("ITEM"),itemNo);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item attribute (%d).\n"), es);
      delete pIt;
      return;
    }
    
    // Get phantom state of each BOM row item
    bool data1;
    es = acmBomMgr->getItemExtendedData(itemId,AcmBOMManager::ItemDataType::ePhantomState,data1);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item extended data (%d).\n"), es);
      delete pIt;
      return;
    }
    CString sData1;
    sData1 = data1 ? _T("Yes") : _T("No");

    // Get exclude from part list state of each BOM row item
    bool data2;
    es = acmBomMgr->getItemExtendedData(itemId,AcmBOMManager::ItemDataType::eExcludeFromPartlistState,data2);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item extended data (%d).\n"), es);
      delete pIt;
      return;
    }
    CString sData2;
    sData2 = data2 ? _T("Yes") : _T("No");

    acutPrintf(_T("\nItem: %s Phantomed: %s Excluded From Part List: %s.\n"), itemNo, sData1, sData2);
  }
  delete pIt;  
}

// Set the boolean value of a specific data associated with a specific bom item
//
void setItemExtendedData()
{
  AcDbObjectId BTRId;
  if( !selectBTRId(BTRId)) return;

  // Get BOM table
  CString name;
  AcDbObjectId bomId;
  Acad::ErrorStatus es = acmBomMgr->getBomTable(bomId,name,BTRId);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for getting bom table (%d).\n"), es);
    return;
  }

  // Specify BOM row item no
  CString itNo;
  acedInitGet(RSG_NONULL,_T(""));
  int res = acedGetString(0,_T("\nItem Number to exclude from part list:"),itNo.GetBuffer(150));
  itNo.ReleaseBuffer();
  if( res == RTCAN || res == RTERROR ) return;

  // Get BOM row item for the specified item no
  AcmIterator *pIt = acmBomMgr->newItemIterator(bomId);
  CString itemNo;
  AcDbObjectId itemId;  
  Adesk::Boolean found = Adesk::kFalse;
  for( ; !pIt->done(); pIt->next()) {
    itemId =  pIt->objectId();
    es = acmBomMgr->getItemAttribute(itemId,_T("ITEM"),itemNo);
    if( Acad::eOk != es ) {
      acutPrintf(_T("\nError code returned for getting item attribute (%d).\n"), es);
      delete pIt;
      return;
    }
    if( itemNo == itNo ) {
      found = Adesk::kTrue;
      break;
    }
  }
  delete pIt;
  if( !found ) { 
    acutPrintf(_T("\nError - specified item number not found !"));
    return;
  }

  // Set specified BOM row item to exclude from part list
  es = acmBomMgr->setItemExtendedData(itemId,AcmBOMManager::ItemDataType::eExcludeFromPartlistState,true);
  if( Acad::eOk != es ) {
    acutPrintf(_T("\nError code returned for setting item extended data (%d).\n"), es);
    delete pIt;
    return;
  }
}

