/////////////////////////////////////////////////////////////////////////////// 
// Copyright (C) 2003-2012, Open Design Alliance (the "Alliance"). 
// All rights reserved. 
// 
// This software and its documentation and related materials are owned by 
// the Alliance. The software may only be incorporated into application 
// programs owned by members of the Alliance, subject to a signed 
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable  
// trade secrets of the Alliance and its suppliers. The software is also 
// protected by copyright law and international treaty provisions. Application  
// programs incorporating this software must include the following statement 
// with their copyright notices:
//   
//   This application incorporates Teigha(R) software pursuant to a license 
//   agreement with Open Design Alliance.
//   Teigha(R) Copyright (C) 2003-2012 by Open Design Alliance. 
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you 
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "DbUserIO.h"
#include "DbDatabase.h"
#include "DbBaseDatabase.h"
#include "DbHostAppServices.h"
#include "ExDbCommandContext.h"
#include "Ge/GeExtents2d.h"
#include "Ge/GeMatrix3d.h"
#include "Ge/GeLineSeg2d.h"
#include "DbLine.h"
#include "DbDictionary.h"
#include "DbColor.h"
#include "StaticRxObject.h"
#include "Gi/GiDrawableImpl.h"
#include "Gi/GiWorldDraw.h"
#include "Gi/GiViewportDraw.h"
#include "Gi/GiUtils.h"
#include "OdDToStr.h"
#include "Gs/Gs.h"
#include "DbViewport.h"
#include "DbLayerTableRecord.h"
#include "DbBlockTableRecord.h"
#include "Ed/EdLispEngine.h"

#include <math.h>

inline OdString next(OdString& list, const OdChar* delim = (const OdChar*)L",")
{
  OdString res = list.spanExcluding(delim);
  if(res.getLength() != list.getLength())
    list = list.mid(res.getLength()+1);
  else
    list.empty();
  return res;
}

OdRxDictionaryPtr ExDbCommandContext::arbDataDic() const
{
  if(m_pArbDataDic.isNull())
    m_pArbDataDic = odrxCreateRxDictionary();
  return m_pArbDataDic;
}

void ExDbCommandContext::setParam(OdRxObject* pParamObj)
{
  setArbitraryData(OD_T("ExDbCommandContext_FuncParamObj"), pParamObj);
}

OdRxObjectPtr ExDbCommandContext::param()
{
  return arbitraryData(OD_T("ExDbCommandContext_FuncParamObj"));
}

void ExDbCommandContext::setResult(OdRxObject* pResultObj)
{
  setArbitraryData(OD_T("ExDbCommandContext_FuncResultObj"), pResultObj);
}

OdRxObjectPtr ExDbCommandContext::result()
{
  return arbitraryData(OD_T("ExDbCommandContext_FuncResultObj"));
}

void ExDbCommandContext::setArbitraryData(const OdString& szPathName, OdRxObject* pDataObj)
{
  arbDataDic()->putAt(szPathName, pDataObj);
}

OdRxObjectPtr ExDbCommandContext::arbitraryData(const OdString& szPathName) const
{
  return arbDataDic()->getAt(szPathName);
}

OdSelectionSetPtr ExDbCommandContext::pickfirst()
{
  return arbitraryData(OD_T("ExDbCommandContext_pickfirst"));
}

void ExDbCommandContext::setPickfirst(OdSelectionSet* pSSet)
{
  setArbitraryData(OD_T("ExDbCommandContext_pickfirst"), pSSet);
}

OdSelectionSetPtr ExDbCommandContext::previousSelection()
{
  return arbitraryData(OD_T("ExDbCommandContext_PreviousSSet"));
}

void ExDbCommandContext::setPreviousSelection(OdSelectionSet* pSSet)
{
  setArbitraryData(OD_T("ExDbCommandContext_PreviousSSet"), pSSet);
}

OdUnitsFormatter& ExDbCommandContext::baseFormatter()
{
  OdDbBaseDatabasePEPtr pDbPE = OdDbBaseDatabasePE::cast(baseDatabase());
  //ODA_ASSERT_ONCE(pDbPE.get());
  if (!pDbPE.get())
    throw OdError(eNoDatabase); 
  return *pDbPE->baseFormatter(baseDatabase());
}

OdDbUnitsFormatter& ExDbCommandContext::formatter()
{
  return database()->formatter();
}

OdDbCommandContextPtr ExDbCommandContext::createObject(OdEdBaseIO* pIOStream, OdDbDatabase* pDb)
{
  OdDbCommandContextPtr pRes = OdRxObjectImpl<ExDbCommandContext, OdDbCommandContext>::createObject();
  ExDbCommandContext* pDbCmdCtx = static_cast<ExDbCommandContext*>(pRes.get());

  pDbCmdCtx->m_pDb = pDb;
  pDbCmdCtx->m_pIoStream = pIOStream;
  return pRes;
}

OdRxObject* ExDbCommandContext::baseDatabase()
{
  return m_pDb;
}

OdSelectionSetPtr ExDbCommandContext::box(const OdGePoint3d& pt1, OdDbVisualSelection::SubentSelectionMode ssm)
{
  OdGePoint3d pts[2] = {
    pt1,
    dbUserIO()->getPoint(OD_T("Specify opposite corner:"), OdEd::kGptNoLimCheck|OdEd::kGptNoUCS|OdEd::kGptRectFrame)
  };

  return OdDbSelectionSet::select(database()->activeViewportId(), 2, pts, OdDbVisualSelection::kBox, ssm);
}

/*
bool pointInsideSeg(const OdGePoint2d& point, const OdGeLineSeg2d& seg)
{
  return !(point.isEqualTo(seg.startPoint()) || point.isEqualTo(seg.endPoint()));
}

void OdExSelectCmd::appendSelectPoint(const OdGePoint2d& point)
{
  if((m_interactionMode != kSelectFence) && (m_selectPoints.size() > 2))
  {
    // self-intersecting countours can't be passed as arguments for window or crossing selection
    OdGePoint2d pt(point.x, point.y);
    OdGeLineSeg2d seg1(OdGePoint2d(m_selectPoints[0].x, m_selectPoints[0].y), pt), 
                  seg2(OdGePoint2d(m_selectPoints.last().x, m_selectPoints.last().y), pt);
    OdUInt32 i;
    bool bIntersFound(false);
    for(i = 0; i < m_selectPoints.size()-1; i ++)
    {
      OdGeLineSeg2d seg(OdGePoint2d(m_selectPoints[i].x, m_selectPoints[i].y), 
                        OdGePoint2d(m_selectPoints[i+1].x, m_selectPoints[i+1].y));
      OdGePoint2d intPt;
      if(seg1.intersectWith(seg, intPt) && pointInsideSeg(intPt, seg1) || 
         seg2.intersectWith(seg, intPt) && pointInsideSeg(intPt, seg2))
      {
        bIntersFound = true;
        break;
      }
    }
    if(bIntersFound)
    {
      dispSelectError();
      return ;
    }
    else
    {
      dispSelectPrompt(); // clear last error
    }
  }
  m_selectPoints.push_back(point);
}
*/

void ExDbCommandContext::highlight(OdSelectionSet* pSSet, 
                                   bool bDoIt) const //  = true
{
  for (OdSelectionSetIteratorPtr pIter = pSSet->newIterator();
       !pIter->done(); pIter->next())
    highlight(pIter, bDoIt);
}

void ExDbCommandContext::highlight(OdSelectionSetIterator* pIter, 
                                   bool bDoIt) const //  = true
{
  OdDbObjectPtr pObj = OdDbObjectId(pIter->id()).safeOpenObject();
  if (!pObj->isKindOf(OdDbEntity::desc()))
    return;
  OdDbEntityPtr pEnt = pObj;
  if (pIter->subentCount())
  {
    for (OdUInt32 se = 0; se < pIter->subentCount(); se++)
    {
      OdDbFullSubentPath subEntPath;
      if (pIter->getSubentity(se, subEntPath))
        pEnt->highlight(bDoIt, &subEntPath);
    }
  }
  else
    pEnt->highlight(bDoIt);
}

inline bool isOverallVp(const OdDbEntity* pObj)
{
  OdDbViewportPtr pVp = OdDbViewport::cast(pObj);
  if(pVp.get() && pVp->number()==1)
    return true;
  return false;
}

bool ExDbCommandContext::isPickadd() const
{
  return true;
}

void ExDbCommandContext::merge(OdSelectionSet* pRes,
                               int options,
                               const OdSelectionSet* pSSet,
                               OdSSetTracker* pTracker,
                               OdEdUserIO* pIO) const //  = NULL
{
  ODA_ASSERT(pSSet && pRes && pRes->baseDatabase() == pSSet->baseDatabase());

  if (!pSSet)
    return;
  OdString s;
  if (pIO)
  {
    s.format(OD_T("%d found"), pSSet->numEntities());
    pIO->putString(s);
  }
  
  int nOnLockedLayer = 0;
  int nNotInCurrentSpace = 0;
  int nObjects = 0;
  bool bPsVp = false;

  if (!isPickadd() && pRes->numEntities() && pSSet->numEntities())
  {
    highlight(pRes, false);
    pRes->clear();
  }

  bool bAppending = !GETBIT(options, OdEd::kSelRemove);
  OdDbObjectId BTRId;
  OdDbDatabase* pDb = OdDbDatabase::cast(pRes->baseDatabase()).get();
  if (pDb)
    BTRId = pDb->getActiveLayoutBTRId();
  OdSelectionSetIteratorPtr pIter;

  for (pIter = pSSet->newIterator(); !pIter->done(); pIter->next())
  {
    OdDbStub* id = pIter->id();
    ODA_ASSERT_ONCE(id && pSSet->baseDatabase() == baseDatabaseBy(id));

    bool bMember = pRes->isMember(id);
    if (bMember && GETBIT(options, OdEd::kSelAllowSubents))
    {
      for (OdUInt32 se = 0; se < pIter->subentCount(); se++)
      {
        OdDbBaseFullSubentPath subEntPath;
        pIter->getSubentity(se, subEntPath);
        if (!pRes->isMember(subEntPath))
        {
          bMember = false;
          break;
        }
      }
    }
    if (!(bMember ^ bAppending))
      continue;
    if (pDb)
    {
      OdDbEntityPtr pEnt = OdDbEntity::cast(OdDbObjectId(id).openObject());
      if (pEnt.get())
      {
        OdDbLayerTableRecordPtr l = pEnt->layerId().openObject();
        if (!GETBIT(options, OdEd::kSelAllowLocked) && !l.isNull() && l->isLocked())
        {
          ++nOnLockedLayer;
          continue;
        }
        else if(!GETBIT(options, OdEd::kSelAllowInactSpaces) && pEnt->ownerId()!=BTRId)
        {
          ++nNotInCurrentSpace;
          continue;
        }
        else if(!bPsVp && !GETBIT(options, OdEd::kSelAllowPSVP) && ::isOverallVp(pEnt))
        {
          bPsVp = true;
          continue;
        }
      }
      else if (!GETBIT(options, OdEd::kSelAllowObjects))
      {
        ++nObjects;
        continue;
      }
    }
      
    highlight(pIter, bAppending);
    if (bAppending)
    {
      if (!pTracker || pTracker->append(id, pIter->method()))
      {
        pRes->append(id, pIter->method());
        for (OdUInt32 se = 0; se < pIter->subentCount(); se++)
        {
          OdDbBaseFullSubentPath subEntPath;
          if (pIter->getSubentity(se, subEntPath))
            pRes->append(subEntPath, pIter->method());
        }
      }
    }
    else
    {
      if (!pTracker || pTracker->remove(id, pIter->method()))
      {
        if (GETBIT(options, OdEd::kSelAllowSubents))
        {
          for (OdSelectionSetIteratorPtr pResIter = pRes->newIterator();
               !pResIter->done(); pResIter->next())
          {
            if (pResIter->id() != id)
              continue;
            
            if (pResIter->subentCount())
            {
              for (OdUInt32 se = 0; se < pIter->subentCount(); se++)
              {
                OdDbBaseFullSubentPath subEntPath;
                if (pIter->getSubentity(se, subEntPath))
                  pRes->remove(subEntPath);
              }
              if (!pResIter->subentCount())
                pRes->remove(id);
            }
            break;
          } // end for
        }
        else
          pRes->remove(id);
      }
    }
  } // end for

  if (pIO && nObjects)
  {
    if (nObjects == 1)
      s = OD_T("1 was not an entity.");
    else
      s.format(OD_T("%d were not entities."), nObjects);
    pIO->putString(s);
  }
  if (pIO && nNotInCurrentSpace)
  {
    if (nNotInCurrentSpace == 1)
      s = OD_T("1 was not in current space.");
    else
      s.format(OD_T("%d were not in current space."), nNotInCurrentSpace);
    pIO->putString(s);
  }
  if (pIO && nOnLockedLayer)
  {
    if (nOnLockedLayer == 1)
      s = L"1 was on a locked layer.";
    else
      s.format(L"%d were on a locked layer.", nOnLockedLayer);
    pIO->putString(s);
  }
  if (pIO && bPsVp)
  {
    pIO->putString(OD_T("1 was the paper space viewport."));
  }
}

class PolylineSelectionTracker : public OdEdPointDefTracker, public OdGiDrawableImpl<>
{
protected:
  ODRX_USING_HEAP_OPERATORS(OdEdPointDefTracker);
  const OdGePoint3dArray *m_pts;
  OdGePoint3d m_pt;
  //PolylineSelectionTracker() : m_pts(NULL) { }
  void setValue(const OdGePoint3d &pt)
  { 
    m_pt = pt; 
  }
  void setValue(const OdGePoint3dArray *pts)
  {
    m_pts = pts;
  }
  OdUInt32 subSetAttributes(OdGiDrawableTraits* ) const ODRX_OVERRIDE
  {
    return kDrawableIsAnEntity;
  }
  bool subWorldDraw(OdGiWorldDraw* pWd) const ODRX_OVERRIDE
  {
    if (m_pts && m_pts->size() > 1)
    {
      pWd->geometry().polyline((OdInt32)m_pts->size(), m_pts->asArrayPtr());
    }
    if (m_pts && m_pts->size() > 0)
    {
      OdGePoint3d tailPts[2] =
      {
        m_pts->last(),
        m_pt
      };
      pWd->geometry().polyline(2, tailPts);
    }
    return true;
  }
  OdGePoint3d basePoint() const
  {
    if (m_pts && !m_pts->empty())
      return m_pts->first();
    return OdGePoint3d::kOrigin;
  }
public:
  static OdEdPointTrackerPtr create(const OdGePoint3dArray *pts = NULL)
  {
    OdEdPointTrackerPtr pRes = OdRxObjectImpl<PolylineSelectionTracker, OdEdPointTracker>::createObject();
    static_cast<PolylineSelectionTracker*>(pRes.get())->setValue(pts);
    return pRes;
  }

  int addDrawables(OdGsView* pView)
  {
    pView->add(this, 0);
    return 1;
  }
  void removeDrawables(OdGsView* pView)
  {
    pView->erase(this);
  }
};

class PolygonSelectionTracker : public PolylineSelectionTracker
{
protected:
  bool subWorldDraw(OdGiWorldDraw* pWd) const
  {
    PolylineSelectionTracker::subWorldDraw(pWd);
    // Polygon segments must not intersect. This check must be implemented in selection command.
    if (m_pts && m_pts->size() > 1)
    {
      OdGePoint3d closePts[2] =
      {
        m_pt,
        m_pts->first()
      };
      pWd->geometry().polyline(2, closePts);
    }
    return true;
  }
public:
  static OdEdPointTrackerPtr create(const OdGePoint3dArray *pts = NULL)
  {
    OdEdPointTrackerPtr pRes = OdRxObjectImpl<PolygonSelectionTracker, OdEdPointTracker>::createObject();
    static_cast<PolygonSelectionTracker*>(pRes.get())->setValue(pts);
    return pRes;
  }
  static bool doesPolySegmentsNotIntersects(const OdGePoint3dArray &segments, const OdGePoint3d &newPoint)
  {
    if (segments.size() < 3)
      return true;
    OdGePoint3dArray poly = segments;
    //poly.push_back(newPoint);
    OdGeVector3d zAxis = ::odgiFaceNormal(poly.size(), poly.asArrayPtr());
    if (OdZero(zAxis.length()))
      return true; // All segments are parallel
    poly.push_back(newPoint);
    OdUInt32 nSeg;
    OdGeVector3d xAxis;
    for (nSeg = 0; nSeg < poly.size() - 1; nSeg++)
    {
      xAxis = segments[nSeg + 1] - segments[nSeg];
      OdGe::ErrorCondition error;
      xAxis.normalize(OdGeContext::gZeroTol, error);
      if (error == OdGe::kOk)
        break;
    }
    OdGeVector3d yAxis = zAxis.crossProduct(xAxis).normal();
    OdGePoint2dArray poly2d;
    poly2d.resize(poly.size());
    for (nSeg = 0; nSeg < poly.size(); nSeg++)
      poly2d[nSeg].set(poly[nSeg].asVector().dotProduct(xAxis), poly[nSeg].asVector().dotProduct(yAxis));
    // 1-st segment with all without last
    for (nSeg = 0; nSeg < poly2d.size() - 3; nSeg++)
    {
      OdGePoint2d rVal;
      if (OdGeLineSeg2d(poly2d[nSeg], poly2d[nSeg + 1]).intersectWith(OdGeLineSeg2d(poly2d[poly2d.size() - 2], poly2d.last()), rVal))
        return false;
    }
    // 2-nd segment with all without first
    for (nSeg = 1; nSeg < poly2d.size() - 2; nSeg++)
    {
      OdGePoint2d rVal;
      if (OdGeLineSeg2d(poly2d[nSeg], poly2d[nSeg + 1]).intersectWith(OdGeLineSeg2d(poly2d.first(), poly2d.last()), rVal))
        return false;
    }
    return true;
  }
};

class ExSelectionMethods
{
  protected:
    // Returns true if entity is on visible layer
    static bool isEntityVisible(OdDbEntityPtr pEntity)
    {
      if (!pEntity.isNull() && pEntity->visibility() == OdDb::kVisible)
      {
        OdDbLayerTableRecordPtr pLayer = OdDbLayerTableRecord::cast(pEntity->layerId().openObject(OdDb::kForRead));
        if (!pLayer->isFrozen() && !pLayer->isOff())
          return true;
      }
      return false;
    }
    // Return object Id of cirrent selection block
    static OdDbObjectId selectionBlockId(const OdDbObjectId& vpId)
    {
      OdDbDatabase* pDb = vpId.database();
      OdDbViewportPtr pVp = OdDbViewport::cast(vpId.safeOpenObject());
      if (!pVp.isNull())
        return (pVp->number() == 1) ? pDb->getPaperSpaceId() : pDb->getModelSpaceId();
      return pDb->getModelSpaceId();
    }
  public:
    static void selectLast(OdDbDatabase *pDb, OdDbObjectIdArray &ids)
    {
      OdDbObjectId blockId = selectionBlockId(pDb->activeViewportId());
      OdDbBlockTableRecordPtr pBlockRec = OdDbBlockTableRecord::cast(blockId.openObject(OdDb::kForRead));
      OdDbObjectIteratorPtr pIt = pBlockRec->newIterator(false);
      while (!pIt->done())
      {
        OdDbEntityPtr pEntity = pIt->entity(OdDb::kForRead);
        if (isEntityVisible(pEntity))
        {
          ids.append(pEntity->id());
          break;
        }
        pIt->step(false);
      }
    }
    static void selectAll(OdDbDatabase *pDb, OdDbObjectIdArray &ids)
    {
      OdDbObjectId blockId = selectionBlockId(pDb->activeViewportId());
      OdDbBlockTableRecordPtr pBlockRec = OdDbBlockTableRecord::cast(blockId.openObject(OdDb::kForRead));
      OdDbObjectIteratorPtr pIt = pBlockRec->newIterator();
      while (!pIt->done())
      {
        OdDbEntityPtr pEntity = pIt->entity(OdDb::kForRead);
        if (isEntityVisible(pEntity))
        {
          ids.append(pEntity->id());
        }
        pIt->step();
      }
    }
    static void selectRect(OdSmartPtr<OdEdBaseIO> pIoStream, OdDbUserIO* pIO, OdDbVisualSelection::SubentSelectionMode &ssm, OdGePoint3dArray &pts)
    {
      pts.resize(2);
      pts[0] = pIO->getPoint(OD_T("Specify first corner:"),    OdEd::kGptNoLimCheck|OdEd::kGptNoUCS);
      if ((pIoStream->getKeyState() & OdEdBaseIO::kControlIsDown) == 0)
        ssm = OdDbVisualSelection::kDisableSubents;
      pts[1] = pIO->getPoint(OD_T("Specify opposite corner:"), OdEd::kGptNoLimCheck|OdEd::kGptNoUCS|OdEd::kGptRectFrame);
    }
    static void selectPolyline(OdSmartPtr<OdEdBaseIO> pIoStream, OdDbUserIO* pIO, OdDbVisualSelection::SubentSelectionMode &ssm, OdGePoint3dArray &pts)
    {
      pts.clear();
      OdEdPointTrackerPtr pTracker = PolylineSelectionTracker::create(&pts);
      for (;;)
      {
        if (pts.size() == 0)
        {
          pts.resize(1);
          pts[0] = pIO->getPoint(OD_T("Specify first fence point:"), OdEd::kGptNoLimCheck|OdEd::kGptNoUCS);
        }
        if ((pIoStream->getKeyState() & OdEdBaseIO::kControlIsDown) == 0)
          ssm = OdDbVisualSelection::kDisableSubents;
        try
        {
          pts.append(pIO->getPoint(OD_T("Specify next fence point:"),
                                   OdEd::kGptNoLimCheck|OdEd::kGptNoUCS|/*OdEd::kGptRubberBand|*/
                                   OdEd::kInpThrowEmpty/*|OdEd::kInpThrowOther*/,
                                   0, OD_T("Undo"), pTracker));
        }
        catch(const OdEdKeyword& kw)
        {
          if (kw.keywordIndex() == 0)
            pts.removeLast();
        }
        catch (const OdEdEmptyInput)
        {
          break;
        }
      }
    }
    static void selectPolygon(OdSmartPtr<OdEdBaseIO> pIoStream, OdDbUserIO* pIO, OdDbVisualSelection::SubentSelectionMode &ssm, OdGePoint3dArray &pts)
    {
      pts.clear();
      OdEdPointTrackerPtr pTracker = PolygonSelectionTracker::create(&pts);
      for (;;)
      {
        if (pts.size() == 0)
        {
          pts.resize(1);
          pts[0] = pIO->getPoint(OD_T("First polygon point:"), OdEd::kGptNoLimCheck|OdEd::kGptNoUCS);
        }
        if ((pIoStream->getKeyState() & OdEdBaseIO::kControlIsDown) == 0)
          ssm = OdDbVisualSelection::kDisableSubents;
        try
        {
          OdGePoint3d newPoint = pIO->getPoint(OD_T("Specify endpoint of line:"),
                                               OdEd::kGptNoLimCheck|OdEd::kGptNoUCS|
                                               OdEd::kInpThrowEmpty,
                                               0, OD_T("Undo"), pTracker);
          if (PolygonSelectionTracker::doesPolySegmentsNotIntersects(pts, newPoint))
          {
            pts.append(newPoint);
          }
          else
          {
            pIO->putString(OD_T("Invalid point, polygon segments cannot intersect."));
          }
        }
        catch(const OdEdKeyword& kw)
        {
          if (kw.keywordIndex() == 0)
            pts.removeLast();
        }
        catch (const OdEdEmptyInput)
        {
          break;
        }
      }
    }
};

OdSelectionSetPtr ExDbCommandContext::select(const OdString& sPrompt,
                                             int options,
                                             const OdSelectionSet* pDefVal,
                                             const OdString& sKeywords,
                                             OdSSetTracker* pBaseTracker)
{
  OdEdSSetTracker* pTracker = OdEdSSetTracker::cast(pBaseTracker).get();
  OdDbUserIO* pIO = dbUserIO();
  OdDbDatabase* pDb = database();
  OdDbSelectionSetPtr pRes = OdDbSelectionSet::createObject(pDb);
  
  OdString sMsg;
  OdDbSelectionSetPtr pSSet = pickfirst();
  setPickfirst(0);
  OdDbSelectionSetIteratorPtr pIter;

  if( pSSet.get() && pSSet->numEntities() && !pDefVal && !GETBIT(options, OdEd::kSelIgnorePickFirst) )
  {
    merge(pRes, (options | OdEd::kSelRemove) & ~OdEd::kSelRemove, pSSet, pTracker, pIO);
    return pRes;
  }
  pSSet.release();

  OdString prompt(sPrompt);
  if (prompt.isEmpty())
    prompt = OD_T("Select objects:");

  OdString keywords(sKeywords);
  if (keywords.isEmpty())
    keywords = OD_T("Window Last Crossing BOX ALL Fence WPolygon CPolygon Group Add Remove Multiple Previous Undo Auto Single");

  if (pDefVal)
    merge(pRes, (options | OdEd::kSelRemove) & ~OdEd::kSelRemove, pDefVal, pTracker, pIO);

  OdGePoint3dArray pts;
  do
  {
selection_loop_start:
    OdDbVisualSelection::SubentSelectionMode ssm = GETBIT(options, OdEd::kSelAllowSubents)? OdDbVisualSelection::kEnableSubents : OdDbVisualSelection::kDisableSubents;
    int nKword = -1;
    pts.resize(1);
    try
    {
      try
      {
        if(GETBIT(options, OdEd::kSelPickLastPoint))
        {
          SETBIT(options, OdEd::kSelPickLastPoint, false);
          pts[0] = pIO->getLASTPOINT();
        }
        else
        {
          pts[0] = pIO->getPoint(prompt, 
            OdEd::kGptNoUCS|
            OdEd::kGptNoLimCheck|
            OdEd::kInpThrowEmpty|
            OdEd::kInpThrowOther,
            0, keywords);
        }
        if ((m_pIoStream->getKeyState() & OdEdBaseIO::kControlIsDown) == 0)
          ssm = OdDbVisualSelection::kDisableSubents;
        pSSet = OdDbSelectionSet::select(database()->activeViewportId(), 1, &pts[0], OdDbVisualSelection::kPoint, ssm);
        if(pSSet->numEntities()==0 && !GETBIT(options, OdEd::kSelSingleEntity))
        {
          pSSet = box(pts[0], ssm);
        }
      }
      catch(const OdEdOtherInput& inp)
      {
        if(try_lisp(inp.string()))
        {
          pSSet = OdDbSelectionSet::createObject(pDb);
          pSSet->append(OdResBufPtr(result())->getObjectId(pDb));
        }
        else
          throw;
      }
    }
    catch(const OdEdKeyword& kw)
    {
      nKword = kw.keywordIndex();
    }
    catch(const OdEdEmptyInput)
    {
      if(pRes->numEntities() || GETBIT(options, OdEd::kSelAllowEmpty))
        break;

      if(GETBIT(options, OdEd::kInpThrowEmpty))
        throw;

      continue;
    }
    catch(const OdEdOtherInput& )
    {
      if(GETBIT(options, OdEd::kInpThrowOther))
        throw;

      continue;
    }

    bool bNYI = false;
    switch(nKword)
    {
    case -1: // no op
      break;
    case  0: // Window
      ExSelectionMethods::selectRect(m_pIoStream, pIO, ssm, pts);
      pSSet = OdDbSelectionSet::select(database()->activeViewportId(), 2, pts.getPtr(), OdDbVisualSelection::kWindow, ssm);
      break;
    case  1: // Last
      {
        OdDbObjectIdArray ids;
        pSSet = OdDbSelectionSet::createObject(database());
        ExSelectionMethods::selectLast(database(), ids);
        pSSet->append(ids);
      }
      break;
    case  2: // Crossing
      ExSelectionMethods::selectRect(m_pIoStream, pIO, ssm, pts);
      pSSet = OdDbSelectionSet::select(database()->activeViewportId(), 2, pts.getPtr(), OdDbVisualSelection::kCrossing, ssm);
      break;
    case  3: // BOX
      pts[0] = pIO->getPoint(L"Specify first corner:", OdEd::kGptNoLimCheck|OdEd::kGptNoUCS);
      if ((m_pIoStream->getKeyState() & OdEdBaseIO::kControlIsDown) == 0)
        ssm = OdDbVisualSelection::kDisableSubents;
      pSSet = box(pts[0], ssm);
      break;
    case  4: // ALL
      {
        OdDbObjectIdArray ids;
        pSSet = OdDbSelectionSet::createObject(database());
        ExSelectionMethods::selectAll(database(), ids);
        pSSet->append(ids);
      }
      break;
    case  5: // Fence
      ExSelectionMethods::selectPolyline(m_pIoStream, pIO, ssm, pts);
      if (pts.size() > 1)
        pSSet = OdDbSelectionSet::select(database()->activeViewportId(), (int)pts.size(), pts.getPtr(), OdDbVisualSelection::kFence, ssm);
      break;
    case  6: // WPolygon
      ExSelectionMethods::selectPolygon(m_pIoStream, pIO, ssm, pts);
      if (pts.size() > 2)
        pSSet = OdDbSelectionSet::select(database()->activeViewportId(), (int)pts.size(), pts.getPtr(), OdDbVisualSelection::kWPoly, ssm);
      break;
    case  7: // CPolygon
      ExSelectionMethods::selectPolygon(m_pIoStream, pIO, ssm, pts);
      if (pts.size() > 2)
        pSSet = OdDbSelectionSet::select(database()->activeViewportId(), (int)pts.size(), pts.getPtr(), OdDbVisualSelection::kCPoly, ssm);
      break;
    case  8: // Group
      bNYI = true;
      break;
    case  9: // Add
      SETBIT(options, OdEd::kSelRemove, false);
      break;
    case 10: // Remove
      SETBIT(options, OdEd::kSelRemove, true);
      break;
    case 11: // Multiple
      SETBIT(options, OdEd::kSelSinglePass, false);
      break;
    case 12: // Previous
      pSSet = previousSelection();
      break;
    case 13: // Undo
      bNYI = true;
      break;
    case 14: // AUto
      SETBIT(options, OdEd::kSelRemove, false); // Same as 'Add'
      SETBIT(options, OdEd::kSelSinglePass, false); // Same as 'Multiple'
      break;
    case 15: // SIngle
      SETBIT(options, OdEd::kSelSinglePass, true);
      goto selection_loop_start;
    }
    if (!pSSet.isNull())
      merge(pRes, options, pSSet, pTracker, pIO);
    else if (bNYI)
      pIO->putString(OD_T("Not Yet Implemented"));
  }
  while(!GETBIT(options, OdEd::kSelSinglePass));

  if ( !GETBIT(options, OdEd::kSelLeaveHighlighted) )
    highlight(pRes, false);
  setPreviousSelection(pRes);
  return pRes;
}

KWIndex::KWIndex(const OdString& kwList)
{
  clear();
  int i = 0;
  const OdChar* szKwList = kwList;
  while(szKwList && szKwList[i])
  {
    while(szKwList[i]==' ') // trim
      ++i;
    if(!szKwList[i])
      break;
    if(szKwList[i]=='_')
    {
      ++i;
      break;
    }

    KWIndexData& data = *append();
    int j;
    for(j = i; szKwList[j]!=' ' && szKwList[j]!=',' && szKwList[j]!='\0'; ++j)
    { }
    data.m_sKword =
    data.m_sKey = OdString(szKwList + i, j-i);

    i=j;
    if(szKwList[i]==',')
    {
      ++i;
      while(szKwList[i]==' ') // trim
        ++i;

      int k;
      for (k = i; szKwList[k]!=' ' && szKwList[k]!=',' && szKwList[k]!='\0'; ++k)
      { }
      data.m_sKey2 = OdString(szKwList + i, k-i);
    }
    else
    {
      data.m_sKey2.empty();
    }
  }
  unsigned int kwi = 0;
  while(szKwList && szKwList[i] && kwi < size())
  {
    while(szKwList[i]==' ') // trim
      ++i;
    if(!szKwList[i])
      break;

    KWIndexData& data = at(kwi++);
    int j;
    for(j = i; szKwList[j]!=' ' && szKwList[j]!=',' && szKwList[j]!='\0'; ++j)
    { }
    data.m_sKword = OdString(szKwList + i, j-i);
    i=j;
    if(szKwList[i]==',')
    {
      ++i;
      while(szKwList[i]==' ') // trim
        ++i;
      int k;
      for(k = i; szKwList[k]!=' ' && szKwList[k]!=',' && szKwList[k]!='\0'; ++k)
      { }
      i=k;
    }
  }
}

int KWIndex::find(const OdString& input) const
{
  if(input.isEmpty())
    return -1;

  for(unsigned int i=0; i<size(); ++i)
  {
    const KWIndexData& data = at(i);
    if(data.match(input))
      return i;
  }
  return -1;
}

void KWIndex::check(const OdString& input) const
{
  int n = find(input);
  if(n > -1)
    throw ::OdEdKeyword(n, at(n).keyword());
}

OdString ExDbCommandContext::getStringInternal(const OdString& prompt, int options, OdEdStringTracker* pTracker)
{
  return m_pIoStream->getString(prompt, options, pTracker);
}

OdGePoint3d ExDbCommandContext::getPointOrReal(const OdString& prompt,
                                               int options,
                                               const OdString& keywords,
                                               OdResBuf::ValueType realType,
                                               OdEdPointTracker* pPtTracker)
{
  for(;;)
  {
    try
    {
      return getPoint(prompt, options|OdEd::kInpThrowOther,
                      0, keywords, pPtTracker);
    }
    catch(const OdEdOtherInput& other)
    {
      const OdChar* reprompt = NULL;
      RealInput real = {0};
      try
      {
        switch(realType)
        {
        case OdResBuf::kDxfXdDist:
          if(pPtTracker)
          {
            if(!keywords.isEmpty())
              reprompt = OD_T("Requires numeric distance, second point, or option keyword.");
            else
              reprompt = OD_T("Requires numeric distance or second point.");
          }
          else
          {
            if(!keywords.isEmpty())
              reprompt = OD_T("Requires numeric distance, two points, or option keyword.");
            else
              reprompt = OD_T("Requires numeric distance or two points.");
          }
          real.val = baseFormatter().unformatLinear(other.string());
          break;
        case OdResBuf::kRtOrient:
          if(pPtTracker)
          {
            if(!keywords.isEmpty())
              reprompt = OD_T("Requires valid numeric angle, second point, or option keyword.");
            else
              reprompt = OD_T("Requires valid numeric angle or second point.");
          }
          else
          {
            if(!keywords.isEmpty())
              reprompt = OD_T("Requires valid numeric angle, two points, or option keyword.");
            else
              reprompt = OD_T("Requires valid numeric angle or two points.");
          }
          real.val = baseFormatter().unformatAngle(other.string());
          break;
        default:
          break;
        }
      }
      catch(const OdError& e)
      {
        if(e.code()!=eInvalidInput)
          throw;
        if(GETBIT(options, OdEd::kInpThrowOther))
          throw other;
        m_pIoStream->putString(reprompt);
        continue;
      }
      throw real;
    }
  }
}

class PointToDistTracker : public OdEdPointTracker
{
  OdGePoint3d       m_basePoint;
  OdEdRealTracker*  m_pTracker;
  int               m_options;
  OdRxObject*       m_pRxDb;
  mutable OdGsView* m_pGsView;
public:
  static OdEdPointTrackerPtr create(const OdGePoint3d& basePoint, int options, OdEdRealTracker* pTracker, 
                                    OdRxObject* pRxDb)
  {
    OdSmartPtr<PointToDistTracker> pRes = OdRxObjectImpl<PointToDistTracker>::createObject();
    pRes->m_basePoint = basePoint;
    pRes->m_options = options;
    pRes->m_pTracker = pTracker;
    pRes->m_pRxDb = pRxDb;
    pRes->m_pGsView = NULL;
    return pRes;
  }

  double getDistByOptions(const OdGePoint3d& pt) const
  {
    OdDbDatabase* pDb = OdDbDatabase::cast(m_pRxDb).get();

    OdGeVector3d vect = pt - m_basePoint;
    if (GETBIT(m_options, OdEd::kGdsSignedDist) && pDb)
      vect = pDb->formatter().toUCS(pt) - pDb->formatter().toUCS(m_basePoint);
    if (GETBIT(m_options, OdEd::kGds2d))
      vect.z = 0;
    double dist = vect.length();
    if (!pDb || !m_pGsView || !GETBIT(m_options, OdEd::kGdsPerpDist | OdEd::kGdsSignedDist))
      return dist;

    OdGeVector3d vUcsDirection(pDb->getUCSXDIR().crossProduct(pDb->getUCSYDIR()));
    OdGePoint3d ptPos = m_pGsView->position(),
                ptTarget = m_pGsView->target();
    OdGeVector3d vDirection = ptPos - ptTarget;

    if (GETBIT(m_options, OdEd::kGdsPerpDist)) // is in perpendicular plane to UCS one
    {
      vUcsDirection.normalize();
      vDirection.normalize();
      double dAng = vDirection.angleTo(vUcsDirection),
             dCoef = tan(dAng);
      if (!OdZero(dCoef))
        dist /= dCoef;
    }

    if (GETBIT(m_options, OdEd::kGdsSignedDist))
    {
      OdGeVector3d vTst = pDb->getUCSXDIR() + pDb->getUCSYDIR();
      if ((vDirection.dotProduct(vTst) < 0.0) == (vect.dotProduct(vTst) < 0.0))
        dist = - dist;
    }
    return dist;
  }

  void setValue(const OdGePoint3d& pt)
  {
    m_pTracker->setValue(getDistByOptions(pt));
  }

  int addDrawables(OdGsView* pView) 
  { 
    m_pGsView = pView;
    return m_pTracker->addDrawables(pView); 
  }
  void removeDrawables(OdGsView* pView) 
  { 
    //m_pGsView = NULL;
    m_pTracker->removeDrawables(pView); 
  }
};

class PointToOrientTracker : public OdEdPointTracker
{
  OdGePoint3d       m_basePoint;
  OdEdRealTracker*  m_pTracker;
  OdGeMatrix3d      m_xUcsToWorld;
public:
  static OdEdPointTrackerPtr create(const OdGePoint3d& basePoint, OdEdRealTracker* pTracker)
  {
    OdSmartPtr<PointToOrientTracker> pRes;
    /*
    = OdRxObjectImpl<PointToOrientTracker>::createObject();
    pRes->m_basePoint = basePoint;
    pRes->m_pTracker = pTracker;
    */
#pragma MARKMESSAGE(TODO : implement PointToOrientTracker)
    return pRes;
  }
  void setValue(const OdGePoint3d& pt)
  {
    m_pTracker->setValue((pt-m_basePoint).length());
  }

  int addDrawables(OdGsView* pView) 
  { 
    return m_pTracker->addDrawables(pView); 
  }
  void removeDrawables(OdGsView* pView) 
  { 
    m_pTracker->removeDrawables(pView); 
  }
};

double ExDbCommandContext::getReal(const OdString& sPrompt,
                                   int options,
                                   double defVal,
                                   OdResBuf::ValueType vt,
                                   const OdString& sKeywords,
                                   OdEdRealTracker* pTracker)
{
  OdString prompt ( sPrompt );
  if(prompt.isEmpty())
    prompt = OD_T("Enter a distance:");

  double resDist;
  OdGePoint3d basePt;
  if (!GETBIT(options, 1))
  {
    try
    {
      basePt = getPointOrReal(prompt, options, sKeywords, vt, 0);
    }
    catch(const RealInput& dist)
    {
      if(pTracker)
        pTracker->setValue(dist.val);
      return dist.val;
    }
    prompt = OD_T("Specify second point:");
  }
  else
  {
    basePt = dbUserIO()->getLASTPOINT();
  }
  
  OdEdPointTrackerPtr pPtTracker;
  if(pTracker)
  {
    if(vt==OdResBuf::kDxfXdDist)
      pPtTracker = PointToDistTracker::create(basePt, options, pTracker, m_pDb);
    else
      pPtTracker = PointToOrientTracker::create(basePt, pTracker);
  }

  try
  {
    OdGePoint3d pt = getPointOrReal(prompt, options, sKeywords, vt, pPtTracker);
    resDist = (pt - basePt).length();
    if (pTracker && vt == OdResBuf::kDxfXdDist && GETBIT(options, OdEd::kGdsSignedDist))
      resDist = ((PointToDistTracker*) pPtTracker.get())->getDistByOptions(pt);
  }
  catch(const RealInput& dist)
  {
    resDist = dist.val;
  }
  if (pTracker)
    pTracker->setValue(resDist);
  return resDist;
}

double ExDbCommandContext::getDist(const OdString& sPrompt,
                                   int options,
                                   double defVal,
                                   const OdString& sKeywords,
                                   OdEdRealTracker* pTracker)
{
  OdString prompt( sPrompt );
  if(prompt.isEmpty())
    prompt = OD_T("Enter a distance:");

  return getReal(prompt, options, defVal, OdResBuf::kDxfXdDist, sKeywords, pTracker);
}

class ANGBASE_Tracker : public OdEdRealTracker
{
  OdEdRealTracker*  m_pAngleTracker;
  double            m_ANGBASE;
protected:
  ANGBASE_Tracker() {}
public:
  static OdEdRealTrackerPtr create(OdEdRealTracker* pAngleTracker, double ANGBASE)
  {
    OdSmartPtr<ANGBASE_Tracker> pRes = OdRxObjectImpl<ANGBASE_Tracker>::createObject();
    pRes->m_pAngleTracker = pAngleTracker;
    pRes->m_ANGBASE = ANGBASE;
    return pRes;
  }
  void setValue(double orient)
  {
    m_pAngleTracker->setValue(orient - m_ANGBASE);
  }

  int addDrawables(OdGsView* pView) 
  { 
    return m_pAngleTracker->addDrawables(pView); 
  }
  void removeDrawables(OdGsView* pView) 
  { 
    m_pAngleTracker->removeDrawables(pView); 
  }
};

double ExDbCommandContext::getAngle(const OdString& sPrompt,
                                    int options,
                                    double defVal,
                                    const OdString& sKeywords,
                                    OdEdRealTracker* pTracker)
{
  OdString prompt(sPrompt);

  if (prompt.isEmpty())
    prompt = OD_T("Specify an angle:");

  double ANGBASE = 0.0;
  //OdEdRealTrackerPtr pTrck; // TODO define angle via points
  if (GETBIT(options, OdEd::kGanNoAngBase))
  {
    if (OdDbDatabase::cast(baseDatabase()).get())
      ANGBASE = database()->getANGBASE();
    //if (pTracker)
    //  pTrck = ANGBASE_Tracker::create(pTracker, ANGBASE);
  }
  // #4907 //return getReal(prompt, options, defVal + ANGBASE, sKeywords, pTrck) - ANGBASE;
  for (;;)
  {
    OdString sInput = ExDbCommandContext::getString(prompt, options, OdString::kEmpty, sKeywords);
    if (sInput.isEmpty())
      return defVal;
    try
    {
      double dAngle = baseFormatter().unformatAngle(sInput);
      dAngle -= ANGBASE;
      return dAngle;
    }
    catch (const OdError& e)
    {
      if (e.code() != eInvalidInput)
        throw;
      if (GETBIT(options, OdEd::kInpThrowOther))
        throw OdEdOtherInput(sInput);
      putString(sKeywords.find(L' ') < 0 ? L"Requires valid numeric angle."
                                         : L"Requires valid numeric angle or optional keyword.");
    }
  }
}

class RubberBand : public OdEdPointDefTracker, public OdGiDrawableImpl<>
{
  ODRX_USING_HEAP_OPERATORS(OdEdPointDefTracker);
  OdGePoint3d m_pts[2];
  OdGsModelPtr m_pModel;

  OdUInt32 subSetAttributes(OdGiDrawableTraits* ) const ODRX_OVERRIDE
  {
    return kDrawableIsAnEntity;
  }
  bool subWorldDraw(OdGiWorldDraw* pWd) const ODRX_OVERRIDE
  {
    pWd->geometry().polyline(2, m_pts);
    return true;
  }
  OdGePoint3d basePoint() const
  {
    return m_pts[0];
  }
public:
  static OdEdPointTrackerPtr create(const OdGePoint3d& base, OdGsModel* pModel = NULL)
  {
    OdEdPointTrackerPtr pRes = OdRxObjectImpl<RubberBand, OdEdPointTracker>::createObject();
    static_cast<RubberBand*>(pRes.get())->m_pts[0] = base;
    static_cast<RubberBand*>(pRes.get())->m_pModel = pModel;
    return pRes;
  }

  void setValue(const OdGePoint3d& p)
  {
    m_pts[1] = p;
    OdGiDrawable* pParent = NULL;
    if (!m_pModel.isNull())
      m_pModel->onModified(this, pParent);
  }
  int addDrawables(OdGsView* pView)
  {
    pView->add(this, m_pModel.get());
    return 1;
  }
  void removeDrawables(OdGsView* pView)
  {
    pView->erase(this);
  }
};

OdEdPointDefTrackerPtr ExDbCommandContext::createRubberBand(const OdGePoint3d& base,
                                                            OdGsModel* pModel) const // = NULL
{
  return RubberBand::create(base, pModel);
}

class RectFrame : public OdEdPointDefTracker, public OdGiDrawableImpl<>
{
  ODRX_USING_HEAP_OPERATORS(OdEdPointDefTracker);
  mutable OdGePoint3d m_pts[4];
  OdGsModelPtr m_pModel;

  OdUInt32 subSetAttributes(OdGiDrawableTraits* ) const ODRX_OVERRIDE
  {
    return kDrawableIsAnEntity;
  }
  bool subWorldDraw(OdGiWorldDraw* pWd) const ODRX_OVERRIDE
  {
    return false;
  }
  void subViewportDraw(OdGiViewportDraw* pVd) const ODRX_OVERRIDE
  {
    const OdGiViewport& vp = pVd->viewport();
    OdGeMatrix3d x = vp.getWorldToEyeTransform();
    OdGePoint3d p0 = x * m_pts[0];
    OdGePoint3d p2 = x * m_pts[2];
    
    m_pts[1].x = p0.x;
    m_pts[3].x = p2.x;
    m_pts[1].y = p2.y;
    m_pts[3].y = p0.y;
    m_pts[1].z =
    m_pts[3].z = p2.z;
    
    x = vp.getEyeToWorldTransform();
    m_pts[1].transformBy(x);
    m_pts[3].transformBy(x);

    if (!m_pModel.isNull())
    {
      // fix invalid plane clipping of selection rectangle for some DGN drawing (like samp4.dgn)
      bool clipFront = false, 
           clipBack = false;
      double front, 
             back;
      if (   vp.getFrontAndBackClipValues(clipFront, clipBack, front, back)
          && clipFront && clipBack)
      {
        //OdGePoint3d ptBase = m_pts[0];
        OdGePoint3d ptBase = vp.getCameraLocation();
        ptBase += vp.viewDir().normalize() * ((front + back) / 2.0);
        OdGePlane plane(ptBase, vp.viewDir());
        for (int index = 0; index < 4; index++)
        {
          if (!plane.isOn(m_pts[index]))
          {
            OdGePoint3d ptProj;
            if (plane.project(m_pts[index], ptProj))
              m_pts[index] = ptProj;
          }
        }
      }
    }

    pVd->subEntityTraits().setFillType(kOdGiFillNever);
    pVd->geometry().polygon(4, m_pts);
  }
  OdGePoint3d basePoint() const
  {
    return m_pts[0];
  }
public:
  static OdEdPointTrackerPtr create(const OdGePoint3d& base, OdGsModel* pModel)
  {
    OdEdPointTrackerPtr pRes = OdRxObjectImpl<RectFrame, OdEdPointTracker>::createObject();
    static_cast<RectFrame*>(pRes.get())->m_pts[0] = base;
    static_cast<RectFrame*>(pRes.get())->m_pModel = pModel;
    return pRes;
  }
  void setValue(const OdGePoint3d& p)
  {
    m_pts[2] = p;
    OdGiDrawable* pParent = NULL;
    if (!m_pModel.isNull())
      m_pModel->onModified(this, pParent);
  }
  int addDrawables(OdGsView* pView)
  {
    pView->add(this, m_pModel.get());
    return 1;
  }
  void removeDrawables(OdGsView* pView)
  {
    pView->erase(this);
  }
};

OdEdPointDefTrackerPtr ExDbCommandContext::createRectFrame(const OdGePoint3d& base,
                                                           OdGsModel* pModel) const // = NULL
{
  return RectFrame::create(base, pModel);
}

OdGePoint3d ExDbCommandContext::getLASTPOINT() const
{
  return m_LASTPOINT;
}

void ExDbCommandContext::setLASTPOINT(const OdGePoint3d& pt)
{
  m_LASTPOINT = pt;
}

OdGePoint3d ExDbCommandContext::getPoint(const OdString& sPrompt,
                                         int options,
                                         const OdGePoint3d* pDefVal,
                                         const OdString& keywords,
                                         OdEdPointTracker* pTracker)
{
  OdString prompt(sPrompt);

  if(prompt.isEmpty())
    prompt = OD_T("Enter a point:");

  OdEdPointTrackerPtr pPredef;
  if(!pTracker)
  {
    if(GETBIT(options, OdEd::kGptRectFrame))
    {
      pTracker = pPredef = createRectFrame(dbUserIO()->getLASTPOINT());
    }
    else if(GETBIT(options, OdEd::kGptRubberBand))
    {
      pTracker = pPredef = createRubberBand(dbUserIO()->getLASTPOINT());
    }
  }

  for(;;)
  {
    OdGePoint3d res;
    try
    {
      res = m_pIoStream->getPoint( prompt, options, pTracker );
    }
    catch(const OdEdEmptyInput&)
    {
      if(GETBIT(options, OdEd::kInpThrowEmpty))
        throw;
    }
    catch(const OdEdOtherInput& other)
    {
      if(!other.string().isEmpty())
      {
        if(!keywords.isEmpty())
          KWIndex(keywords).check(other.string());
        try
        {
          res = OdGePoint3d::kOrigin;
          OdString sInput(other.string());
          int i = 0;
          for (; i < 3 && !sInput.isEmpty(); ++i)
          {
            OdString sCoord = ::next(sInput);
            res[i] = baseFormatter().unformatLinear(sCoord);
          }
          if (i < 2)
            throw;
        }
        catch(const OdError& e)
        {
          if(e.code()!=eInvalidInput)
            throw;
          
          if(GETBIT(options, OdEd::kInpThrowOther))
            throw other;
          
          m_pIoStream->putString(keywords.isEmpty() ? OD_T("Invalid point.") : OD_T("Point or option keyword required."));
          continue;
        }
      }
      else if (GETBIT(options, OdEd::kInpThrowEmpty))
      {
        throw OdEdEmptyInput();
      }
      else
      {
        continue;
      }
    }
    if(!GETBIT(options, OdEd::kGptNoLimCheck) && database()->getLIMCHECK())
    {
      OdGeExtents2d ext(database()->getLIMMIN(), database()->getLIMMAX());
      if(!ext.contains((const OdGePoint2d&)res))
      {
        putString(OD_T("**Outside limits"));
        continue;
      }
    }
    
    dbUserIO()->setLASTPOINT(res);
    return res;
  }
}

int ExDbCommandContext::getInt(const OdString& sPrompt,
                               int options,
                               int defVal,
                               const OdString& sKeywords,
                               OdEdIntegerTracker* pTracker)
{
  OdString prompt(sPrompt);

  if(prompt.isEmpty())
    prompt = OD_T("Enter an integer value:");
  OdString sInput = ExDbCommandContext::getString(prompt, 0, OdString::kEmpty, sKeywords);
  if (sInput.isEmpty())
    return defVal;
  return ::odStrToInt(sInput);
}

int ExDbCommandContext::getKeyword(const OdString& sPrompt,
                                   const OdString& sKeywords,
                                   int defVal,
                                   int options,
                                   OdEdIntegerTracker* pTracker)
{
  OdString prompt(sPrompt);

  KWIndex kwIndex(sKeywords);
  OdString pmt;
  if(prompt.isEmpty())
  {
    pmt = OD_T("Enter one of keywords [");
    for(OdUInt32 i=0; i<kwIndex.size(); ++i)
    {
      pmt += kwIndex.at(i).keyword();
      pmt += OD_T("/");
    }
    pmt += OD_T("]");
    if(!GETBIT(options, OdEd::kInpThrowEmpty))
    {
      pmt += OD_T("<");
      pmt += kwIndex.at(defVal).keyword();
      pmt += OD_T(">");
    }
    pmt += OD_T(":");
    prompt = pmt;
  }

  bool bDef = (OdUInt32(defVal) < kwIndex.size());
  for(;;)
  {
    SETBIT(options, OdEd::kGstNoEmpty, !bDef);
    OdString sInput = ExDbCommandContext::getString(prompt, options);
    if(bDef && sInput.isEmpty())
      return defVal;
    OdUInt32 n = kwIndex.find(sInput);
    if(n < kwIndex.size())
      return n;
    putString(OD_T(""));
    putString(OD_T("Invalid option keyword."));
  }
}

double ExDbCommandContext::getReal(const OdString& sPrompt, 
                                   int options,
                                   double defVal,
                                   const OdString& sKeywords,
                                   OdEdRealTracker* pTracker)
{
  OdString prompt(sPrompt);

  if(prompt.isEmpty())
    prompt = OD_T("Enter a real value:");
  for(;;)
  {
    OdString sInput = ExDbCommandContext::getString(prompt, options, OdString::kEmpty, sKeywords);
    if (sInput.isEmpty())
      return defVal;
    try
    {
      return baseFormatter().unformatLinear(sInput);
    }
    catch(const OdError& e)
    {
      if(e.code()!=eInvalidInput)
        throw;
      if(GETBIT(options, OdEd::kInpThrowOther))
        throw OdEdOtherInput(sInput);
      putString(sKeywords.find(L' ') < 0 ? L"Requires real value." 
                                         : L"Requires real value or optional keyword.");
    }
  }
}

OdString ExDbCommandContext::getString(const OdString& sPrompt, 
                                       int options, 
                                       const OdString& pDefVal,
                                       const OdString& sKeywords,
                                       OdEdStringTracker* pTracker)
{
  OdString res = getStringInternal(sPrompt, options, pTracker);

  if(res.isEmpty())
  {
    if(GETBIT(options, OdEd::kInpThrowEmpty))
      throw OdEdEmptyInput();

    if(!pDefVal.isEmpty())
      return pDefVal;

    if(GETBIT(options, OdEd::kGstNoEmpty))
    {
      do
      {
        putString(OD_T("Enter non-empty value"));
        res = getStringInternal(sPrompt, options, pTracker);
      }
      while(res.isEmpty());
    }
    else
      return res;
  }

  if(!sKeywords.isEmpty())
    KWIndex(sKeywords).check(res);

  return res;
}

OdString ExDbCommandContext::getFilePath(const OdString& prompt, 
                                         int options,
                                         const OdString& dialogCaption, 
                                         const OdString& defExt,
                                         const OdString& fileName,
                                         const OdString& filter,
                                         const OdString& keywords,
                                         OdEdStringTracker* pTracker)
{
  OdString sInput;
  if(!database()->appServices()->getFILEDIA())
  {
    sInput = OdDbUserIO::getFilePath(prompt, options, dialogCaption, defExt, fileName, filter, keywords, pTracker);
    if(sInput != OD_T("~"))
    {
      sInput.trimLeft(' ');
      sInput.trimRight(' ');
      return sInput;
    }
  }

  sInput = database()->appServices()->fileDialog(options & 3, dialogCaption, defExt, fileName, filter);
  if( sInput == OD_T("*unsupported*") )
    sInput = OdDbUserIO::getFilePath(prompt, options, dialogCaption, defExt, fileName, filter, keywords, pTracker);
  return sInput;
}

void ExDbCommandContext::putString(const OdString& string)
{
  m_pIoStream->putString(string);
}

OdCmColor ExDbCommandContext::getColor(const OdString& sPrompt,
                                       int options,
                                       const OdCmColor* pDefVal,
                                       const OdString& sKeywords,
                                       OdEdColorTracker* pTracker)
{
  database(); // throw OdError(eNoDatabase) if it is not DWG
  OdSharedPtr<OdCmColorBase> color = getCmColor(sPrompt, options, pDefVal, sKeywords, pTracker);
  OdCmColor* pColor = dynamic_cast<OdCmColor*>(color.get());
  ODA_ASSERT_ONCE(pColor);
  if (!pColor)
    throw OdError(eInvalidInput);
  return *pColor;
}

OdSharedPtr<OdCmColorBase> ExDbCommandContext::getCmColor(const OdString& sPrompt,
                                                          int options,
                                                          const OdCmColorBase* pDefVal,
                                                          const OdString& sKeywords,
                                                          OdEdColorTracker* pTracker)
{
  OdString prompt(sPrompt);

  if(prompt.isEmpty())
    prompt = OD_T("Specify a color:");

  for(;;)
  {
    OdString sInput = ExDbCommandContext::getString(
                          prompt, options,
                          pDefVal ? (const OdString&)baseFormatter().formatCmColor(*pDefVal) 
                                  : OdString::kEmpty, sKeywords);
    try
    {
      return baseFormatter().unformatCmColor(sInput);
    }
    catch(const OdError& e)
    {
      if(e.code()!=eInvalidInput)
        throw;
      if(GETBIT(options, OdEd::kInpThrowOther))
        throw OdEdOtherInput(sInput);
      putString(sKeywords.find(L' ') < 0 ? L"Invalid color value."
                                         : L"Requires color value or optional keyword.");
    }
  }
}

bool ExDbCommandContext::try_lisp(const OdString& input)
{
  OdString s(input);
  if (s[0]==L'(')
  {
    OdEdLispModulePtr lspMod = odrxDynamicLinker()->loadApp(OdLspModuleName);
    if (lspMod.isNull())
    {
      s.trimLeft(OD_T("( \r\n\t"));
      if(s.left(7).makeLower() == OD_T("handent"))
      {
        s = s.right(s.getLength() - 7);
        s.trimLeft(OD_T(" \r\n\t\""));
        OdUInt64 handle = 0;

        if( odSScanf(s, PRIx64W, &handle) )
        {
          s.trimLeft(OD_T("0123456789ABCDEFabcdef"));
          s.trimLeft(OD_T(" \r\n\t\""));
          if(s[0]==L')')
          {
            OdResBufPtr pRb = OdResBuf::newRb(OdResBuf::kRtHandle);
            pRb->setHandle(OdDbHandle(handle));
            setResult(pRb);
            return true;
          }
        }
      }
    }
    else
    {
      lspMod->createLispEngine()->execute(this, s);
    }
  }
  return false;
}
