/***************************************************************************
 *   Copyright (c) 2012 Thomas Anderson <blobfish[at]gmx.com>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
# include <QBoxLayout>
# include <QHeaderView>
# include <QTextEdit>
# include <QTextStream>
# include <QTreeWidget>
#endif

#include <Standard_Version.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepCheck_Result.hxx>
#include <BRepCheck_ListIteratorOfListOfStatus.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepTools_ShapeSet.hxx>

#if OCC_VERSION_HEX >= 0x060600
#include <BOPAlgo_ArgumentAnalyzer.hxx>
#include <BOPAlgo_ListOfCheckResult.hxx>
#endif

#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <Bnd_Box.hxx>
#include <BRepBndLib.hxx>
#include <ShapeAnalysis_FreeBounds.hxx>
#include <gp_Trsf.hxx>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoResetTransform.h>
#include "../App/PartFeature.h"
#include <Gui/BitmapFactory.h>
#include <Gui/Selection.h>
#include <Gui/Document.h>
#include <Gui/Application.h>
#include <Gui/ViewProvider.h>
#include <Gui/WaitCursor.h>
#include "TaskCheckGeometry.h"

using namespace PartGui;

QVector<QString> buildShapeEnumVector()
{
   QVector<QString>names;
   names.push_back(QObject::tr("Compound"));             //TopAbs_COMPOUND
   names.push_back(QObject::tr("Compound Solid"));       //TopAbs_COMPSOLID
   names.push_back(QObject::tr("Solid"));                //TopAbs_SOLID
   names.push_back(QObject::tr("Shell"));                //TopAbs_SHELL
   names.push_back(QObject::tr("Face"));                 //TopAbs_FACE
   names.push_back(QObject::tr("Wire"));                 //TopAbs_WIRE
   names.push_back(QObject::tr("Edge"));                 //TopAbs_EDGE
   names.push_back(QObject::tr("Vertex"));               //TopAbs_VERTEX
   names.push_back(QObject::tr("Shape"));                //TopAbs_SHAPE
   return names;
}

QString shapeEnumToString(const int &index)
{
    static QVector<QString> names = buildShapeEnumVector();
    if (index < 0 || index > TopAbs_SHAPE)
        return names.at(8);
    return names.at(index);
}

QVector<QString> buildCheckStatusStringVector()
{
    QVector<QString>names;
    names.push_back(QObject::tr("No Error"));                           //    BRepCheck_NoError
    names.push_back(QObject::tr("Invalid Point On Curve"));             //    BRepCheck_InvalidPointOnCurve
    names.push_back(QObject::tr("Invalid Point On Curve On Surface"));  //    BRepCheck_InvalidPointOnCurveOnSurface
    names.push_back(QObject::tr("Invalid Point On Surface"));           //    BRepCheck_InvalidPointOnSurface
    names.push_back(QObject::tr("No 3D Curve"));                        //    BRepCheck_No3DCurve
    names.push_back(QObject::tr("Multiple 3D Curve"));                  //    BRepCheck_Multiple3DCurve
    names.push_back(QObject::tr("Invalid 3D Curve"));                   //    BRepCheck_Invalid3DCurve
    names.push_back(QObject::tr("No Curve On Surface"));                //    BRepCheck_NoCurveOnSurface
    names.push_back(QObject::tr("Invalid Curve On Surface"));           //    BRepCheck_InvalidCurveOnSurface
    names.push_back(QObject::tr("Invalid Curve On Closed Surface"));    //    BRepCheck_InvalidCurveOnClosedSurface
    names.push_back(QObject::tr("Invalid Same Range Flag"));            //    BRepCheck_InvalidSameRangeFlag
    names.push_back(QObject::tr("Invalid Same Parameter Flag"));        //    BRepCheck_InvalidSameParameterFlag
    names.push_back(QObject::tr("Invalid Degenerated Flag"));           //    BRepCheck_InvalidDegeneratedFlag
    names.push_back(QObject::tr("Free Edge"));                          //    BRepCheck_FreeEdge
    names.push_back(QObject::tr("Invalid MultiConnexity"));             //    BRepCheck_InvalidMultiConnexity
    names.push_back(QObject::tr("Invalid Range"));                      //    BRepCheck_InvalidRange
    names.push_back(QObject::tr("Empty Wire"));                         //    BRepCheck_EmptyWire
    names.push_back(QObject::tr("Redundant Edge"));                     //    BRepCheck_RedundantEdge
    names.push_back(QObject::tr("Self Intersecting Wire"));             //    BRepCheck_SelfIntersectingWire
    names.push_back(QObject::tr("No Surface"));                         //    BRepCheck_NoSurface
    names.push_back(QObject::tr("Invalid Wire"));                       //    BRepCheck_InvalidWire
    names.push_back(QObject::tr("Redundant Wire"));                     //    BRepCheck_RedundantWire
    names.push_back(QObject::tr("Intersecting Wires"));                 //    BRepCheck_IntersectingWires
    names.push_back(QObject::tr("Invalid Imbrication Of Wires"));       //    BRepCheck_InvalidImbricationOfWires
    names.push_back(QObject::tr("Empty Shell"));                        //    BRepCheck_EmptyShell
    names.push_back(QObject::tr("Redundant Face"));                     //    BRepCheck_RedundantFace
    names.push_back(QObject::tr("Unorientable Shape"));                 //    BRepCheck_UnorientableShape
    names.push_back(QObject::tr("Not Closed"));                         //    BRepCheck_NotClosed
    names.push_back(QObject::tr("Not Connected"));                      //    BRepCheck_NotConnected
    names.push_back(QObject::tr("Sub Shape Not In Shape"));             //    BRepCheck_SubshapeNotInShape
    names.push_back(QObject::tr("Bad Orientation"));                    //    BRepCheck_BadOrientation
    names.push_back(QObject::tr("Bad Orientation Of Sub Shape"));       //    BRepCheck_BadOrientationOfSubshape
    names.push_back(QObject::tr("Invalid Tolerance Value"));            //    BRepCheck_InvalidToleranceValue
    names.push_back(QObject::tr("Check Failed"));                       //    BRepCheck_CheckFail

    return names;
}

QString checkStatusToString(const int &index)
{
    static QVector<QString> names = buildCheckStatusStringVector();
    if (index == -1)
    {
        return QString(QObject::tr("No Result"));
    }
    if (index > 33 || index < 0)
    {
        QString message(QObject::tr("Out Of Enum Range: "));
        QString number;
        number.setNum(index);
        message += number;
        return message;
    }
    return names.at(index);
}

QVector<QString> buildBOPCheckResultVector()
{
  QVector<QString> results;
  results.push_back(QObject::tr("BOPAlgo CheckUnknown"));               //BOPAlgo_CheckUnknown
  results.push_back(QObject::tr("BOPAlgo BadType"));                    //BOPAlgo_BadType
  results.push_back(QObject::tr("BOPAlgo SelfIntersect"));              //BOPAlgo_SelfIntersect
  results.push_back(QObject::tr("BOPAlgo TooSmallEdge"));               //BOPAlgo_TooSmallEdge
  results.push_back(QObject::tr("BOPAlgo NonRecoverableFace"));         //BOPAlgo_NonRecoverableFace
  results.push_back(QObject::tr("BOPAlgo IncompatibilityOfVertex"));    //BOPAlgo_IncompatibilityOfVertex
  results.push_back(QObject::tr("BOPAlgo IncompatibilityOfEdge"));      //BOPAlgo_IncompatibilityOfEdge
  results.push_back(QObject::tr("BOPAlgo IncompatibilityOfFace"));      //BOPAlgo_IncompatibilityOfFace
  results.push_back(QObject::tr("BOPAlgo OperationAborted"));           //BOPAlgo_OperationAborted
  results.push_back(QObject::tr("BOPAlgo GeomAbs_C0"));                 //BOPAlgo_GeomAbs_C0
  results.push_back(QObject::tr("BOPAlgo NotValid"));                   //BOPAlgo_NotValid
  
  return results;
}

#if OCC_VERSION_HEX >= 0x060600
QString getBOPCheckString(const BOPAlgo_CheckStatus &status)
{
  static QVector<QString> strings = buildBOPCheckResultVector();
  int index = static_cast<int>(status);
  if (index < 0 || index > 10)
    index = 0;
  return strings.at(index);
}
#endif

ResultEntry::ResultEntry()
{
    viewProviderRoot = 0;
    boxSep = 0;
    boxSwitch = 0;
    parent = 0;
    children.clear();
    selectionStrings.clear();
}

ResultEntry::~ResultEntry()
{
    if (boxSep && viewProviderRoot)
        viewProviderRoot->removeChild(boxSep);
    if (viewProviderRoot)
        viewProviderRoot->unref();
    qDeleteAll(children);
}

void ResultEntry::buildEntryName()
{
  ResultEntry *parentEntry = this;
  while(parentEntry->parent != 0)
  {
      ResultEntry *temp = parentEntry->parent;
      if (temp->parent == 0)
        break;
      parentEntry = parentEntry->parent;
  }

  QString stringOut;
  QTextStream stream(&stringOut);
  TopTools_IndexedMapOfShape shapeMap;
  int index(-1);

  switch (this->shape.ShapeType())
  {
  case TopAbs_COMPOUND:
      TopExp::MapShapes(parentEntry->shape, TopAbs_COMPOUND, shapeMap);
      stream << "Compound";
      break;
  case TopAbs_COMPSOLID:
      TopExp::MapShapes(parentEntry->shape, TopAbs_COMPSOLID, shapeMap);
      stream << "CompSolid";
      break;
  case TopAbs_SOLID:
      TopExp::MapShapes(parentEntry->shape, TopAbs_SOLID, shapeMap);
      stream << "Solid";
      break;
  case TopAbs_SHELL:
      TopExp::MapShapes(parentEntry->shape, TopAbs_SHELL, shapeMap);
      stream << "Shell";
      break;
  case TopAbs_WIRE:
      TopExp::MapShapes(parentEntry->shape, TopAbs_WIRE, shapeMap);
      stream << "Wire";
      break;
  case TopAbs_FACE:
      TopExp::MapShapes(parentEntry->shape, TopAbs_FACE, shapeMap);
      stream << "Face";
      break;
  case TopAbs_EDGE:
      TopExp::MapShapes(parentEntry->shape, TopAbs_EDGE, shapeMap);
      stream << "Edge";
      break;
  case TopAbs_VERTEX:
      TopExp::MapShapes(parentEntry->shape, TopAbs_VERTEX, shapeMap);
      stream << "Vertex";
      break;
  default:
      stream << "Unexpected shape type";
      break;
  }

  index = shapeMap.FindIndex(this->shape);
  stream << index;
  this->name = stringOut;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

ResultModel::ResultModel(QObject *parent) : QAbstractItemModel(parent)
{
    root = 0;
}

ResultModel::~ResultModel()
{
    if (root)
        delete root;
}

QModelIndex ResultModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!root)
        return QModelIndex();
    ResultEntry *parentNode = nodeFromIndex(parent);
    if (!parentNode)
        return QModelIndex();
    return createIndex(row, column, parentNode->children.at(row));
}

QModelIndex ResultModel::parent(const QModelIndex &child) const
{
    ResultEntry *childNode = nodeFromIndex(child);
    if (!childNode)
        return QModelIndex();
    ResultEntry *parentNode = childNode->parent;
    if (!parentNode)
        return QModelIndex();
    ResultEntry *grandParentNode = parentNode->parent;
    if (!grandParentNode)
        return QModelIndex();
    int row = grandParentNode->children.indexOf(parentNode);
    return createIndex(row, 0, parentNode);
}

int ResultModel::rowCount(const QModelIndex &parent) const
{
    ResultEntry *parentNode = nodeFromIndex(parent);
    if (!parentNode)
        return 0;
    return parentNode->children.size();
}

int ResultModel::columnCount(const QModelIndex &parent) const
{
    return 3;
}

QVariant ResultModel::data(const QModelIndex &index, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();
    ResultEntry *node = nodeFromIndex(index);
    if (!node)
        return QVariant();
    switch (index.column())
    {
    case 0:
        return QVariant(node->name);
    case 1:
        return QVariant(node->type);
    case 2:
        return QVariant(node->error);
    }
    return QVariant();
}

QVariant ResultModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
        return QVariant();
    switch (section)
    {
    case 0:
        return QVariant(QString(tr("Name")));
    case 1:
        return QVariant(QString(tr("Type")));
    case 2:
        return QVariant(QString(tr("Error")));
    }
    return QVariant();
}

void ResultModel::setResults(ResultEntry *resultsIn)
{
#if QT_VERSION >= 0x040600
    this->beginResetModel();
#endif
    if (root)
        delete root;
    root = resultsIn;
#if QT_VERSION >= 0x040600
    this->endResetModel();
#else
    this->reset();
#endif
}

ResultEntry* ResultModel::getEntry(const QModelIndex &index)
{    
    return nodeFromIndex(index);
}

ResultEntry* ResultModel::nodeFromIndex(const QModelIndex &index) const
{
    if (index.isValid())
        return static_cast<ResultEntry *>(index.internalPointer());
    else
        return root;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

TaskCheckGeometryResults::TaskCheckGeometryResults(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle(tr("Check Geometry"));
    setupInterface();
    setupFunctionMap();
    goCheck();
}

TaskCheckGeometryResults::~TaskCheckGeometryResults()
{
    Gui::Selection().clearSelection();
}

void TaskCheckGeometryResults::setupInterface()
{
    message = new QLabel(this);
    model = new ResultModel(this);
    treeView = new QTreeView(this);
    treeView->setModel(model);
    treeView->setSelectionMode(QAbstractItemView::SingleSelection);
    treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
    connect(treeView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
            this, SLOT(currentRowChanged(QModelIndex,QModelIndex)));

    QVBoxLayout *layout = new QVBoxLayout();
    layout->addWidget(message);
    layout->addWidget(treeView);
    this->setLayout(layout);    
}

void TaskCheckGeometryResults::goCheck()
{
    Gui::WaitCursor wc;
    int selectedCount(0), checkedCount(0), invalidShapes(0);
    std::vector<Gui::SelectionSingleton::SelObj> selection = Gui::Selection().getSelection();
    std::vector<Gui::SelectionSingleton::SelObj>::iterator it;
    ResultEntry *theRoot = new ResultEntry();
    for (it = selection.begin(); it != selection.end(); ++it)
    {
        selectedCount++;
        Part::Feature *feature = dynamic_cast<Part::Feature *>((*it).pObject);
        if (!feature)
            continue;
        currentSeparator = Gui::Application::Instance->activeDocument()->getViewProvider(feature)->getRoot();
        if (!currentSeparator)
            continue;
        TopoDS_Shape shape = feature->Shape.getValue();
        QString baseName;
        QTextStream baseStream(&baseName);
        baseStream << (*it).DocName;
        baseStream << "." << (*it).FeatName;
        if (strlen((*it).SubName) > 0)
        {
            shape = feature->Shape.getShape().getSubShape((*it).SubName);
            baseStream << "." << (*it).SubName;
        }

        if (shape.IsNull())
            continue;
        checkedCount++;
        checkedMap.Clear();
        
        buildShapeContent(baseName, shape);

        BRepCheck_Analyzer shapeCheck(shape);
        if (!shapeCheck.IsValid())
        {
            invalidShapes++;
            ResultEntry *entry = new ResultEntry();
            entry->parent = theRoot;
            entry->shape = shape;
            entry->name = baseName;
            entry->type = shapeEnumToString(shape.ShapeType());
            entry->error = QObject::tr("Invalid");
            entry->viewProviderRoot = currentSeparator;
            entry->viewProviderRoot->ref();
            goSetupResultBoundingBox(entry);
            theRoot->children.push_back(entry);
            recursiveCheck(shapeCheck, shape, entry);
            continue; //don't run BOPAlgo_ArgumentAnalyzer if BRepCheck_Analyzer finds something.
        }
        else
        {
          //BOPAlgo_ArgumentAnalyzer can be really slow!
          //so only run it when the shape seems valid to BRepCheck_Analyzer And
          //when the option is set.
          
          ParameterGrp::handle group = App::GetApplication().GetUserParameter().
          GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod")->GetGroup("Part")->GetGroup("CheckGeometry");
          bool runSignal = group->GetBool("RunBOPCheck", false);
          //for now, user has edit the config file to turn it on.
          //following line ensures that the config file has the setting.
          group->SetBool("RunBOPCheck", runSignal);
          if (runSignal)
            invalidShapes += goBOPSingleCheck(shape, theRoot, baseName);
        }
    }
    model->setResults(theRoot);
    treeView->expandAll();
    treeView->header()->resizeSections(QHeaderView::ResizeToContents);
    QString aMessage;
    QTextStream aStream(&aMessage);
    aStream << checkedCount << " processed out of " << selectedCount << " selected\n";
    aStream << invalidShapes << " invalid shapes.";
    message->setText(aMessage);
    Gui::Selection().clearSelection();
}

void TaskCheckGeometryResults::recursiveCheck(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape,
                                              ResultEntry *parent)
{
    ResultEntry *branchNode = parent;
    BRepCheck_ListIteratorOfListOfStatus listIt;
    if (!shapeCheck.Result(shape).IsNull() && !checkedMap.Contains(shape))
    {
        listIt.Initialize(shapeCheck.Result(shape)->Status());
        if (listIt.Value() != BRepCheck_NoError)
        {
            ResultEntry *entry = new ResultEntry();
            entry->parent = parent;
            entry->shape = shape;
            entry->buildEntryName();
            entry->type = shapeEnumToString(shape.ShapeType());
            entry->error = checkStatusToString(listIt.Value());
            entry->viewProviderRoot = currentSeparator;
            entry->viewProviderRoot->ref();
            dispatchError(entry, listIt.Value());
            parent->children.push_back(entry);
            branchNode = entry;
        }
    }
    checkedMap.Add(shape);

    if (shape.ShapeType() == TopAbs_SOLID)
        checkSub(shapeCheck, shape, TopAbs_SHELL, branchNode);
    if (shape.ShapeType() == TopAbs_EDGE)
        checkSub(shapeCheck, shape, TopAbs_VERTEX, branchNode);
    if (shape.ShapeType() == TopAbs_FACE)
    {
        checkSub(shapeCheck, shape, TopAbs_WIRE, branchNode);
        checkSub(shapeCheck, shape, TopAbs_EDGE, branchNode);
        checkSub(shapeCheck, shape, TopAbs_VERTEX, branchNode);
    }

    for (TopoDS_Iterator it(shape); it.More(); it.Next())
        recursiveCheck(shapeCheck, it.Value(), branchNode);
}

void TaskCheckGeometryResults::checkSub(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape,
                                        const TopAbs_ShapeEnum subType, ResultEntry *parent)
{
    BRepCheck_ListIteratorOfListOfStatus itl;
    TopExp_Explorer exp;
    for (exp.Init(shape,subType); exp.More(); exp.Next())
    {
        const Handle(BRepCheck_Result)& res = shapeCheck.Result(exp.Current());
        const TopoDS_Shape& sub = exp.Current();
        for (res->InitContextIterator(); res->MoreShapeInContext(); res->NextShapeInContext())
        {
            if (res->ContextualShape().IsSame(shape))
            {
                for (itl.Initialize(res->StatusOnShape()); itl.More(); itl.Next())
                {
                     if (itl.Value() == BRepCheck_NoError)
                         break;
                     checkedMap.Add(sub);
                     ResultEntry *entry = new ResultEntry();
                     entry->parent = parent;
                     entry->shape = sub;
                     entry->buildEntryName();
                     entry->type = shapeEnumToString(sub.ShapeType());
                     entry->error = checkStatusToString(itl.Value());
                     entry->viewProviderRoot = currentSeparator;
                     entry->viewProviderRoot->ref();
                     dispatchError(entry, itl.Value());
                     parent->children.push_back(entry);
                }
            }
        }
    }
}

void TaskCheckGeometryResults::buildShapeContent(const QString &baseName, const TopoDS_Shape &shape)
{
  std::ostringstream stream;
  if (!shapeContentString.empty())
    stream << std::endl << std::endl;
  stream << baseName.toLatin1().data() << ":" << std::endl;
  
  BRepTools_ShapeSet set;
  set.Add(shape);
  set.DumpExtent(stream);
  
  shapeContentString += stream.str();
}

QString TaskCheckGeometryResults::getShapeContentString()
{
  return QString::fromStdString(shapeContentString);
}

int TaskCheckGeometryResults::goBOPSingleCheck(const TopoDS_Shape& shapeIn, ResultEntry *theRoot, const QString &baseName)
{
  //ArgumentAnalyser was moved at version 6.6. no back port for now.
#if OCC_VERSION_HEX >= 0x060600
  //Reference use: src/BOPTest/BOPTest_CheckCommands.cxx
  
  //I don't why we need to make a copy, but it doesn't work without it.
  //BRepAlgoAPI_Check also makes a copy of the shape.
  
  //didn't use BRepAlgoAPI_Check because it calls BRepCheck_Analyzer itself and
  //doesnt give us access to it. so I didn't want to run BRepCheck_Analyzer twice to get invalid results.
  
  //BOPAlgo_ArgumentAnalyzer can check 2 objects with respect to a boolean op.
  //this is left for another time.
  TopoDS_Shape BOPCopy = BRepBuilderAPI_Copy(shapeIn).Shape();
  BOPAlgo_ArgumentAnalyzer BOPCheck;
  BOPCheck.SetShape1(BOPCopy);
  //all settings are false by default. so only turn on what we want.
  BOPCheck.ArgumentTypeMode() = true;
  BOPCheck.SelfInterMode() = true;
  BOPCheck.SmallEdgeMode() = true;
  BOPCheck.RebuildFaceMode() = true;
#if OCC_VERSION_HEX >= 0x060700
  BOPCheck.ContinuityMode() = true;
#endif
  BOPCheck.Perform();
  if (!BOPCheck.HasFaulty())
      return 0;

  ResultEntry *entry = new ResultEntry();
  entry->parent = theRoot;
  entry->shape = BOPCopy; //this will cause a problem, with existing entry. i.e. entry is true.
  entry->name = baseName;
  entry->type = shapeEnumToString(shapeIn.ShapeType());
  entry->error = QObject::tr("Invalid");
  entry->viewProviderRoot = currentSeparator;
  entry->viewProviderRoot->ref();
  goSetupResultBoundingBox(entry);
  theRoot->children.push_back(entry);

  const BOPAlgo_ListOfCheckResult &BOPResults = BOPCheck.GetCheckResult();
  BOPAlgo_ListIteratorOfListOfCheckResult BOPResultsIt(BOPResults);
  for (; BOPResultsIt.More(); BOPResultsIt.Next())
  {
    const BOPAlgo_CheckResult &current = BOPResultsIt.Value();
    
    const BOPCol_ListOfShape &faultyShapes1 = current.GetFaultyShapes1();
    BOPCol_ListIteratorOfListOfShape faultyShapes1It(faultyShapes1);
    for (;faultyShapes1It.More(); faultyShapes1It.Next())
    {
      const TopoDS_Shape &faultyShape = faultyShapes1It.Value();
      ResultEntry *faultyEntry = new ResultEntry();
      faultyEntry->parent = entry;
      faultyEntry->shape = faultyShape;
      faultyEntry->buildEntryName();
      faultyEntry->type = shapeEnumToString(faultyShape.ShapeType());
      faultyEntry->error = getBOPCheckString(current.GetCheckStatus());
      faultyEntry->viewProviderRoot = currentSeparator;
      entry->viewProviderRoot->ref();
      goSetupResultBoundingBox(faultyEntry);
      
      if (faultyShape.ShapeType() == TopAbs_FACE)
      {
        goSetupResultTypedSelection(faultyEntry, faultyShape, TopAbs_FACE);
      }
      else if (faultyShape.ShapeType() == TopAbs_EDGE)
      {
        goSetupResultTypedSelection(faultyEntry, faultyShape, TopAbs_EDGE);
      }
      else if (faultyShape.ShapeType() == TopAbs_VERTEX)
      {
        goSetupResultTypedSelection(faultyEntry, faultyShape, TopAbs_VERTEX);
      }
      entry->children.push_back(faultyEntry);
    }
  }
  return 1;
#else
  return 0;
#endif
}


void TaskCheckGeometryResults::dispatchError(ResultEntry *entry, const BRepCheck_Status &stat)
{
    std::vector<FunctionMapType>::iterator mapIt;
    for (mapIt = functionMap.begin(); mapIt != functionMap.end(); ++mapIt)
    {
        if ((*mapIt).get<0>() == entry->shape.ShapeType() && (*mapIt).get<1>() == stat)
        {
            ((*mapIt).get<2>())(entry);
            return;
        }
    }
    goSetupResultBoundingBox(entry);
}

void TaskCheckGeometryResults::setupFunctionMap()
{
    functionMap.push_back(FunctionMapType(TopAbs_SHELL, BRepCheck_NotClosed, goSetupResultShellNotClosed));
    functionMap.push_back(FunctionMapType(TopAbs_WIRE, BRepCheck_NotClosed, goSetupResultWireNotClosed));
    functionMap.push_back(FunctionMapType(TopAbs_VERTEX, BRepCheck_InvalidPointOnCurve, goSetupResultInvalidPointCurve));
    functionMap.push_back(FunctionMapType(TopAbs_FACE, BRepCheck_IntersectingWires, goSetupResultIntersectingWires));
    functionMap.push_back(FunctionMapType(TopAbs_EDGE, BRepCheck_InvalidCurveOnSurface, goSetupResultInvalidCurveSurface));
    functionMap.push_back(FunctionMapType(TopAbs_EDGE, BRepCheck_InvalidSameParameterFlag, goSetupResultInvalidSameParameterFlag));
    functionMap.push_back(FunctionMapType(TopAbs_FACE, BRepCheck_UnorientableShape, goSetupResultUnorientableShapeFace));
}

void TaskCheckGeometryResults::currentRowChanged (const QModelIndex &current, const QModelIndex &previous)
{
    Gui::Selection().clearSelection();
    if (previous.isValid())
    {
        ResultEntry *entry = model->getEntry(previous);
        if (entry)
        {
            if (entry->boxSwitch)
                entry->boxSwitch->whichChild.setValue(SO_SWITCH_NONE);
        }
    }
    if (current.isValid())
    {
        ResultEntry *entry = model->getEntry(current);
        if (entry)
        {
            if (entry->boxSwitch)
                entry->boxSwitch->whichChild.setValue(0);
            QStringList::Iterator stringIt;
            for (stringIt = entry->selectionStrings.begin(); stringIt != entry->selectionStrings.end(); ++stringIt)
            {
                //need unique delimiter.
                QString doc, object, sub;
                if (!this->split((*stringIt), doc, object, sub))
                    continue;
                Gui::Selection().addSelection(doc.toLatin1(), object.toLatin1(), sub.toLatin1());
            }
        }
    }
}

bool TaskCheckGeometryResults::split(QString &input, QString &doc, QString &object, QString &sub)
{
    QStringList strings = input.split(QString::fromLatin1("."));
    if (strings.size() != 3)
        return false;
    doc = strings.at(0);
    object = strings.at(1);
    sub = strings.at(2);
    return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////

QString PartGui::buildSelectionName(const ResultEntry *entry, const TopoDS_Shape &shape)
{
    const ResultEntry *parentEntry = entry;
    while(parentEntry->parent != 0)
    {
        ResultEntry *temp = parentEntry->parent;
        if (temp->parent == 0)
          break;
        parentEntry = parentEntry->parent;
    }

    QString stringOut;
    QTextStream stream(&stringOut);
    stream << parentEntry->name;
    stream << '.';
    TopTools_IndexedMapOfShape shapeMap;
    int index(-1);

    switch (shape.ShapeType())
    {
    case TopAbs_FACE:
        TopExp::MapShapes(parentEntry->shape, TopAbs_FACE, shapeMap);
        stream << "Face";
        break;
    case TopAbs_EDGE:
        TopExp::MapShapes(parentEntry->shape, TopAbs_EDGE, shapeMap);
        stream << "Edge";
        break;
    case TopAbs_VERTEX:
        TopExp::MapShapes(parentEntry->shape, TopAbs_VERTEX, shapeMap);
        stream << "Vertex";
        break;
    default:
        stream << "Unexpected shape type";
        break;
    }

    index = shapeMap.FindIndex(shape);
    stream << index;
    return stringOut;
}

void PartGui::goSetupResultTypedSelection(ResultEntry* entry, const TopoDS_Shape& shape, TopAbs_ShapeEnum type)
{
    TopExp_Explorer it;
    for (it.Init(shape, type); it.More(); it.Next())
    {
        QString name = buildSelectionName(entry, (it.Current()));
        if (!name.isEmpty())
            entry->selectionStrings.append(name);
    }
}

void PartGui::goSetupResultBoundingBox(ResultEntry *entry)
{
    //empty compound throws bounding box error. Mantis #0001426
    try
    {
      Bnd_Box boundingBox;
      BRepBndLib::Add(entry->shape, boundingBox);
      Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
      boundingBox.Get(xmin, ymin, zmin, xmax, ymax, zmax);
      SbVec3f boundCenter((xmax - xmin)/2 + xmin, (ymax - ymin)/2 + ymin, (zmax - zmin)/2 + zmin);
  
      entry->boxSep = new SoSeparator();
      entry->viewProviderRoot->addChild(entry->boxSep);
      entry->boxSwitch = new SoSwitch();
      entry->boxSep->addChild(entry->boxSwitch);
      SoGroup *group = new SoGroup();
      entry->boxSwitch->addChild(group);
      entry->boxSwitch->whichChild.setValue(SO_SWITCH_NONE);
      SoDrawStyle *dStyle = new SoDrawStyle();
      dStyle->style.setValue(SoDrawStyle::LINES);
      dStyle->linePattern.setValue(0xc0c0);
      group->addChild(dStyle);
      SoMaterial *material = new SoMaterial();
      material->diffuseColor.setValue(255.0, 255.0, 255.0);
      material->ambientColor.setValue(255.0, 255.0, 255.0);
      group->addChild(material);

      SoResetTransform *reset = new SoResetTransform();
      group->addChild(reset);
      
      SoTransform *position = new SoTransform();
      position->translation.setValue(boundCenter);
      group->addChild(position);

      SoCube *cube = new SoCube();
      cube->width.setValue(xmax - xmin);
      cube->height.setValue(ymax - ymin);
      cube->depth.setValue(zmax - zmin);
      group->addChild(cube);
    }
    catch (const Standard_Failure &){}
}

void PartGui::goSetupResultShellNotClosed(ResultEntry *entry)
{
    ShapeAnalysis_FreeBounds shellCheck(entry->shape);
    TopoDS_Compound closedWires = shellCheck.GetClosedWires();
    TopoDS_Compound openWires = shellCheck.GetOpenWires();

    goSetupResultTypedSelection(entry, closedWires, TopAbs_EDGE);
    goSetupResultTypedSelection(entry, openWires, TopAbs_EDGE);

    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultWireNotClosed(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_EDGE);
    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultInvalidPointCurve(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_VERTEX);
    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultIntersectingWires(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_FACE);
    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultInvalidCurveSurface(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_EDGE);
    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultInvalidSameParameterFlag(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_EDGE);
    goSetupResultBoundingBox(entry);
}

void PartGui::goSetupResultUnorientableShapeFace(ResultEntry *entry)
{
    goSetupResultTypedSelection(entry, entry->shape, TopAbs_FACE);
    goSetupResultBoundingBox(entry);
}

////////////////////////////////////////////////////////////////////////////////////////////////

TaskCheckGeometryDialog::TaskCheckGeometryDialog() : widget(0), contentLabel(0)
{
    this->setButtonPosition(TaskDialog::South);
    widget = new TaskCheckGeometryResults();
    taskbox = new Gui::TaskView::TaskBox(
        Gui::BitmapFactory().pixmap("Part_CheckGeometry"),
        widget->windowTitle(), false, 0);
    taskbox->groupLayout()->addWidget(widget);
    Content.push_back(taskbox);
    
    contentLabel = new QTextEdit();
    contentLabel->setText(widget->getShapeContentString());
    shapeContentBox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("Part_CheckGeometry"),
        tr("Shape Content"), true, 0);
    shapeContentBox->groupLayout()->addWidget(contentLabel);
    shapeContentBox->hideGroupBox();
    Content.push_back(shapeContentBox);
}

TaskCheckGeometryDialog::~TaskCheckGeometryDialog()
{
  if (widget)
  {
    delete widget;
    widget = 0;
  }
  if (contentLabel)
  {
    delete contentLabel;
    contentLabel = 0;
  }
}

#include "moc_TaskCheckGeometry.cpp"
