//////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2025 Autodesk, Inc.  All rights reserved.
//
//  Use of this software is subject to the terms of the Autodesk license 
//  agreement provided at the time of installation or download, or which 
//  otherwise accompanies this software in either electronic or hard copy form.   
//
//////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "acm.h"
#include "acmdef.h"
#include "cauxent.h"
#include "bommgr.h"
#include "symstd.h"
#include "leader.h"
#include "symbol.h"
#include "bomstd.h"
#include "partlist.h"
#include "stdmgr.h"

Acad::ErrorStatus postToDb(AcDbEntity* ent, AcDbObjectId& objId);

static void FlushGraphic()
{
  actrTransactionManager->queueForGraphicsFlush();
  actrTransactionManager->enableGraphicsFlush(Adesk::kTrue);
  actrTransactionManager->flushGraphics();    
}

class CColumnOwner 
{
public:
  CColumnOwner(const AcDbObjectId& partListId);
  void showActualList();
  void addColumn();
  void removeColumn();
  void run();
private:
  void getColumnList(AcDbIntArray& columnList, Adesk::Boolean notInPartList = Adesk::kFalse);
  AcDbObjectId mPartListId;
};

class CPartListEdit
{
public:
  CPartListEdit();
  Adesk::Boolean OnInit();
  void run();
  void setPListTitle();
  void setHJustify();
  void setScale();
  void setHeadTextHeight();
  void editClmnList();
private:
  AcDbObjectId mPartListId;
};

///////////////////////////////////////
//function which is making the partlist
///////////////////////////////////////
void 
createPartList()
{
  AcDbObjectId currBomId;
  if (acmBomMgr->getCurrentBom(currBomId) != Acad::eOk){ //There is no bom in drawing
    acutPrintf(_T("\nThere is no bom in drawing, prior to calling CREATEPLIST call AMBOM to make one"));
    return; 
  }

  AcGePoint3d pnt;
  int res = ads_getpoint(NULL,_T("\nSelect point :"), asDblArray(pnt));
  if (res == RTCAN || res == RTERROR) return;

  AcmPartList *pPartList = new AcmPartList();
  pPartList->setSymbolDefaults();
  pPartList->setOrigin(pnt);
  pPartList->setBomId(currBomId);
  pPartList->setTitleText(_T("Parts List"));

  //post partlist to the database
  AcDbObjectId partListId;
  if (postToDb(pPartList, partListId) != Acad::eOk) {
    delete pPartList;
    return;
  }

  pPartList->close();

}

///////////////////////////////////////////////
//function, which is calling edit some partlist
///////////////////////////////////////////////
void 
editPList()
{
  CPartListEdit partListEdit;
  if (!partListEdit.OnInit())
    return;
  partListEdit.run();
}

//////////////////////////////////
//CPartListEdit class definition
/////////////////////////////////
CPartListEdit::CPartListEdit()
:mPartListId(AcDbObjectId::kNull)
{
}

Adesk::Boolean
CPartListEdit::OnInit()
{
  Adesk::Boolean b = Adesk::kFalse;
  ads_name entName;
  ads_point pnt;
  //Select entity
  int result = ads_entsel(_T("\nSelec Partlist :"), entName, pnt);
  if (result!=RTNORM)
    return b;
  //Get selected object id
  if (acdbGetObjectId(mPartListId,entName) != Acad::eOk)
    return b;
  //Open object 
  AcDbObject *pObject;
  if (acdbOpenObject(pObject, mPartListId, AcDb::kForRead) != Acad::eOk)
    return b;
  //Is it AcmPartList?
  if (AcmPartList::cast(pObject) != NULL)
    b = Adesk::kTrue;
  //Do not forget close object!
  pObject->close();
  return b;
}

void
CPartListEdit::run()
{
  while (1) {
    acutPrintf(_T("\nMain menu:"));
    acutPrintf(_T("\n1. Set new title."));
    acutPrintf(_T("\n2. Set horizontal justify."));
    acutPrintf(_T("\n3. Set new scale."));
    acutPrintf(_T("\n4. Edit column list."));
    acutPrintf(_T("\n5. Set head text height."));
    int event = 1, result;
    result = ads_getint(_T("\nSelect event <1>:"), &event);
    if (result == RTCAN || result == RTERROR) 
      return;
    switch(event) {
    case 1:
      setPListTitle();
      break;
    case 2:
      setHJustify();
      break;
    case 3:
      setScale();
      break;
    case 4:
      editClmnList();
      break;
    case 5:
      setHeadTextHeight();
      break;
    default:
      break;
    }
  }
}

void 
CPartListEdit::setPListTitle()
{
  TCHAR p[135];
  int result = ads_getstring(1,_T("\nEnter new title for partlist :"), p, 135);
  if (result != RTNORM)
    return;
  AcmPartList* pPartList;
  if (acdbOpenObject(pPartList, mPartListId, AcDb::kForWrite) != Acad::eOk)
    return;
  pPartList->setTitleText(p);
  pPartList->close();
}

void 
CPartListEdit::setHJustify()
{
  acutPrintf(_T("\nSorry, HORIZONTAL JUSTIFY not supported now."));
//  Acm::AlignmentType just[] = {Acm::kLeft, Acm::kRight};
//  acutPrintf("\n ");
//  acutPrintf("Set justify menu:");
//  acutPrintf("\n1. Left.");
//  acutPrintf("\n2. Right.");
//
//  int event = 1;
//  int result = ads_getint("\nSelect event <1>:", &event);
//  if (result == RTCAN || result == RTERROR || event<1 || event>2)
//    return;
//  AcmPartList* pPartList;
//  if (acdbOpenObject(pPartList, mPartListId, AcDb::kForWrite) != Acad::eOk)
//    return;
//  pPartList->setHorizontalDirection(just[--event]);
//  pPartList->close();
}

void 
CPartListEdit::setScale()
{
  double scale = 1.0;
  int result = ads_getreal(_T("\nEnter new scale <1.0> :"), &scale);
  if (result == RTCAN || result == RTERROR)
    return;
  AcmPartList* pPartList;
  if (acdbOpenObject(pPartList, mPartListId, AcDb::kForWrite) != Acad::eOk)
    return;
  pPartList->setScale(scale);
  pPartList->close();
}

void 
CPartListEdit::setHeadTextHeight()
{
  AcmPartList* pPartList;
  if (acdbOpenObject(pPartList, mPartListId, AcDb::kForRead) != Acad::eOk)
    return;
  AcDbObjectId serverId = pPartList->serverId();
  pPartList->close();
  
  AcmBOMStd* pBOMStd;
  if (acdbOpenObject(pBOMStd, serverId, AcDb::kForWrite) != Acad::eOk) 
    return;
  

  AcmCPartListStd* pPLStd = pBOMStd->partListServer();
  //WARNING! Do not close bom standard before end of work with AcmCPartListStd!
  double height = pPLStd->headTextHeight();
  acutPrintf(_T("\nEnter new text height <%.2f> :"), height);
  int result = ads_getreal(NULL, &height);
  if (result == RTCAN || result == RTERROR) {
    pBOMStd->close();
    return;
  }

  pPLStd->setHeadTextHeight(height);
  pBOMStd->close();
  //We changed something in standard. We must call updateSymbols() to 
  //update all symbols now. 
  acmStdMgr->updateSymbols();

  // need this to update part list instantaneously
  FlushGraphic();
}

void
CPartListEdit::editClmnList()
{
  CColumnOwner cOwner(mPartListId);
  cOwner.run();
}

//////////////////////////////////
//CColumnOwner class definition
/////////////////////////////////
CColumnOwner::CColumnOwner(const AcDbObjectId& partListId)
:mPartListId(partListId)
{
}

void 
CColumnOwner::showActualList()
{
  AcDbIntArray clmList;
  getColumnList(clmList);
  const AcmCColumnDef* pClmDef;
  AcmPartList* pPLst;
  if (acdbOpenObject(pPLst, mPartListId, AcDb::kForRead) != Acad::eOk)
    return;
  AcDbObjectId serverId = pPLst->serverId();
  pPLst->close();

  AcmBOMStd* pBOMStd;
  if (acdbOpenObject(pBOMStd, serverId, AcDb::kForRead) != Acad::eOk)
    return;
  acutPrintf(_T("\nCurrent column list:"));
  for (int i = 0; i<clmList.length(); i++) {
    //we must call const columByIndex function, non const call causes crash, because we
    //have BOM standard opened for read only
    pClmDef = ((const AcmBOMStd*)pBOMStd)->columnByIdx(clmList[i]);
    if (pClmDef == NULL)
      continue;
    acutPrintf(_T("\n%s"), pClmDef->name());
  }
  pBOMStd->close();
}

void 
CColumnOwner::addColumn()
{
  AcDbIntArray clmList, currClmList;
  // this call returns all VISIBLE column indexes that actually DO NOT appear in part list
  // (compare with the call of getColumnList in removeColumn())
  getColumnList(clmList, Adesk::kTrue);
  getColumnList(currClmList);
  if (clmList.length() == 0) {
    acutPrintf(_T("\nThere are no columns to adding."));
    return;
  }
  AcmCColumnDef* pClmDef;
  AcmPartList* pPLst;
  if (acdbOpenObject(pPLst, mPartListId, AcDb::kForRead) != Acad::eOk)
    return;
  AcDbObjectId serverId = pPLst->serverId();
  pPLst->close();  

  AcmBOMStd* pBOMStd;
  if (acdbOpenObject(pBOMStd, serverId, AcDb::kForWrite) != Acad::eOk)
    return;
  acutPrintf(_T("\nAvailable column list:"));
  for (int i = 0; i<clmList.length(); i++) {
    pClmDef = pBOMStd->columnByIdx(clmList[i]);
    if (pClmDef == NULL)
      continue;
    acutPrintf(_T("\n%d. %s"), i+1, pClmDef->name());
  }
  
  int clmNumber = 1;
  int result = ads_getint(_T("\nSelect column for adding <1>:"), &clmNumber);
  if (result == RTCAN || result == RTERROR || clmNumber<1 || clmNumber> clmList.length()) {
    acutPrintf(_T("\nInvalid column number."));
    pBOMStd->close();
    return;
  }

  //we will add column now
  int addedClmIndex = clmList[--clmNumber];
  clmList.setLogicalLength(0);
  //we use current columnlist  
  //add new column index to the list
  currClmList.append(addedClmIndex);
  AcmCPartListStd* pListStd = pBOMStd->partListServer();
  //set new column list
  pListStd->setColumnList(currClmList);

  pBOMStd->close(); 

  if (acdbOpenObject(pPLst, mPartListId, AcDb::kForWrite) != Acad::eOk)
    return;
  pPLst->subSymbol()->setColumnList(currClmList);
  pPLst->close();  

  acmStdMgr->updateSymbols();
  
  // need this to update part list instantaneously
  FlushGraphic();
}

void 
CColumnOwner::removeColumn()
{
  AcDbIntArray clmList;
  // this call returns all column indexes that actually appear in part list
  getColumnList(clmList);
  if (clmList.length() == 0) {
    acutPrintf(_T("\nThere are no columns for deleting."));
    return;
  }
  AcmCColumnDef* pClmDef;
  AcmPartList* pPLst;
  if (acdbOpenObject(pPLst, mPartListId, AcDb::kForRead) != Acad::eOk)
    return;
  AcDbObjectId serverId = pPLst->serverId();
  pPLst->close();

  // displays column list
  AcmBOMStd* pBOMStd;
  if (acdbOpenObject(pBOMStd, serverId, AcDb::kForWrite) != Acad::eOk)
    return;
  acutPrintf(_T("\nAvailable column list:"));
  for (int i = 0; i<clmList.length(); i++) {
    pClmDef = pBOMStd->columnByIdx(clmList[i]);
    if (pClmDef == NULL)
      continue;
    acutPrintf(_T("\n%d. %s"), i+1, pClmDef->name());
  }
  

  int clmNumber = 1;
  int result = ads_getint(_T("\nSelect column for deleting <1>:"), &clmNumber);
  if (result == RTCAN || result == RTERROR || clmNumber<1 || clmNumber> clmList.length()) {
    acutPrintf(_T("\nInvalid column number."));
    pBOMStd->close();
    return;
  }

  //we will delete column now
  
  AcmCPartListStd* pListStd = pBOMStd->partListServer();
  //Be careful! Do not never delete pListStd pointer!
  
  //remove column from column list
  pListStd->removeColumnByIdx(clmList[--clmNumber]);

  pBOMStd->close();
  
  if (acdbOpenObject(pPLst, mPartListId, AcDb::kForWrite) != Acad::eOk)
    return;
  clmList.removeAt(clmNumber);
  pPLst->subSymbol()->setColumnList(clmList);
  pPLst->close(); 

  //we changed standard, so we must call following function.
  acmStdMgr->updateSymbols();

  // need this to update part list instantaneously
  FlushGraphic();
}

void
CColumnOwner::run()
{
  while (1) {
    acutPrintf(_T("\n "));
    acutPrintf(_T("\nColumn edit menu:"));
    acutPrintf(_T("\n1. Show actual column list."));
    acutPrintf(_T("\n2. Add column."));
    acutPrintf(_T("\n3. Remove column."));
    int event = 1, result;
    result = ads_getint(_T("\nSelect event <1>:"), &event);
    if (result == RTCAN || result == RTERROR) 
      return;
    switch(event) {
    case 1:
      showActualList();
      break;
    case 2:
      addColumn();
      break;
    case 3:
      removeColumn();
      break;
    default:
      break;
    }
  }
}

void
CColumnOwner::getColumnList(AcDbIntArray& columnList, Adesk::Boolean notInPartList/* = Adesk::kFalse*/)
{
  AcmPartList *pList;
  if (acdbOpenObject(pList, mPartListId, AcDb::kForRead) != Acad::eOk)
    return;
  AcDbObjectId serverId = pList->serverId();
  pList->close();

  AcmBOMStd* pBOMStd;
  if (acdbOpenObject(pBOMStd, serverId, AcDb::kForRead) != Acad::eOk)
    return;

  AcmCPartListStd* pPLStd = pBOMStd->partListServer();

  columnList.setLogicalLength(0);
  if (!notInPartList) {
    pPLStd->getColumnList(columnList);
    pBOMStd->close();
    return;
  }

  AcDbIntArray tmpList;
  pPLStd->getColumnList(tmpList);
  for (int i = 0; i < pBOMStd->columnLength(); i++)
  {
    // filter out colums already present in part list
    if (tmpList.contains(i)) 
      continue;

    // filter out invisible columns
    const AcmCColumnDef* pColDef = ((const AcmBOMStd*)pBOMStd)->columnByIdx(i);
    if (!pColDef || !pColDef->isVisible())
      continue;

    columnList.append(i);
  }   
  
  pBOMStd->close();   
}