/***************************************************************************
 *   Copyright (c) 2013 Jan Rheinländer <jrheinlaender[at]users.sourceforge.net>     *
 *                                                                         *
 *   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 <Standard_math.hxx>
# include <QAction>
# include <QApplication>
# include <QDockWidget>
# include <QMenu>
# include <QStackedWidget>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/nodes/SoBaseColor.h>
# include <Inventor/nodes/SoFontStyle.h>
# include <Inventor/nodes/SoText2.h>
# include <Inventor/nodes/SoTranslation.h>
# include <Inventor/nodes/SoRotation.h>
# include <Inventor/nodes/SoMaterial.h>
# include <Inventor/nodes/SoCylinder.h>
# include <Inventor/nodes/SoCone.h>
# include <Inventor/nodes/SoCube.h>
# include <Inventor/nodes/SoShapeHints.h>
# include <Inventor/nodes/SoComplexity.h>
#endif

#include "ViewProviderFemConstraint.h"
#include "TaskFemConstraint.h"

#include "Gui/Control.h"
#include "Gui/MainWindow.h"
#include "Gui/Command.h"
#include "Gui/Application.h"
#include "Gui/Document.h"

#include <Base/Console.h>

using namespace FemGui;

PROPERTY_SOURCE(FemGui::ViewProviderFemConstraint, Gui::ViewProviderDocumentObject)


ViewProviderFemConstraint::ViewProviderFemConstraint()
{
    ADD_PROPERTY(TextColor,(0.0f,0.0f,0.0f));
    ADD_PROPERTY(FaceColor,(1.0f,0.0f,0.2f));
    ADD_PROPERTY(ShapeColor,(1.0f,0.0f,0.2f));
    ADD_PROPERTY(FontSize,(18));
    ADD_PROPERTY(DistFactor,(1.0));
    ADD_PROPERTY(Mirror,(false));

    pFont = new SoFontStyle();
    pFont->ref();
    pLabel = new SoText2();
    pLabel->ref();
    pTextColor = new SoBaseColor();
    pTextColor->ref();

    pMaterials = new SoBaseColor();
    pMaterials->ref();
    pMaterials->rgb.setValue(1.0f, 0.0f, 0.2f);

    pShapeSep = new SoSeparator();
    pShapeSep->ref();

    TextColor.touch();
    FontSize.touch();
    FaceColor.touch();

    wizardWidget = NULL;
    wizardSubLayout = NULL;
    constraintDialog = NULL;
}

ViewProviderFemConstraint::~ViewProviderFemConstraint()
{
    pFont->unref();
    pLabel->unref();
    pTextColor->unref();
    pMaterials->unref();
    pShapeSep->unref();
}

void ViewProviderFemConstraint::attach(App::DocumentObject* pcObject)
{
    ViewProviderDocumentObject::attach(pcObject);

    SoPickStyle* ps = new SoPickStyle();
    ps->style = SoPickStyle::UNPICKABLE;

    SoSeparator* sep = new SoSeparator();
    SoShapeHints* hints = new SoShapeHints();
    hints->shapeType.setValue(SoShapeHints::UNKNOWN_SHAPE_TYPE);
    hints->vertexOrdering.setValue(SoShapeHints::COUNTERCLOCKWISE);
    sep->addChild(ps);
    sep->addChild(hints);
    sep->addChild(pMaterials);
    sep->addChild(pShapeSep);
    addDisplayMaskMode(sep, "Base");
}

std::vector<std::string> ViewProviderFemConstraint::getDisplayModes(void) const
{
    // add modes
    std::vector<std::string> StrList;
    StrList.push_back("Base");
    return StrList;
}

void ViewProviderFemConstraint::setDisplayMode(const char* ModeName)
{
    if (strcmp(ModeName, "Base") == 0)
        setDisplayMaskMode("Base");
    ViewProviderDocumentObject::setDisplayMode(ModeName);
}

std::vector<App::DocumentObject*> ViewProviderFemConstraint::claimChildren(void)const
{
    return std::vector<App::DocumentObject*>();
}

void ViewProviderFemConstraint::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
    QAction* act;
    act = menu->addAction(QObject::tr("Edit constraint"), receiver, member);
    act->setData(QVariant((int)ViewProvider::Default));
    ViewProviderDocumentObject::setupContextMenu(menu, receiver, member);
}

void ViewProviderFemConstraint::onChanged(const App::Property* prop)
{
    if (prop == &Mirror || prop == &DistFactor) {
        updateData(prop);
    }
    else if (prop == &TextColor) {
        const App::Color& c = TextColor.getValue();
        pTextColor->rgb.setValue(c.r,c.g,c.b);
    }
    else if (prop == &FaceColor) {
        const App::Color& c = FaceColor.getValue();
        pMaterials->rgb.setValue(c.r,c.g,c.b);
    }
    else if (prop == &FontSize) {
        pFont->size = FontSize.getValue();
    }
    else {
        ViewProviderDocumentObject::onChanged(prop);
    }
}

//OvG: Visibility automation show parts and hide meshes on activation of a constraint
std::string ViewProviderFemConstraint::gethideMeshShowPartStr(const std::string showConstr)
{
    return "for amesh in App.activeDocument().Objects:\n\
    if \""+showConstr+"\" == amesh.Name:\n\
        amesh.ViewObject.Visibility = True\n\
    elif \"Mesh\" in amesh.TypeId:\n\
        aparttoshow = amesh.Name.replace(\"_Mesh\",\"\")\n\
        for apart in App.activeDocument().Objects:\n\
            if aparttoshow == apart.Name:\n\
                apart.ViewObject.Visibility = True\n\
        amesh.ViewObject.Visibility = False\n";
}

std::string ViewProviderFemConstraint::gethideMeshShowPartStr()
{
    return ViewProviderFemConstraint::gethideMeshShowPartStr("");
}

bool ViewProviderFemConstraint::setEdit(int ModNum)
{
    Gui::Command::doCommand(Gui::Command::Doc,"%s",ViewProviderFemConstraint::gethideMeshShowPartStr().c_str()); 
    return Gui::ViewProviderGeometryObject::setEdit(ModNum);
}

void ViewProviderFemConstraint::unsetEdit(int ModNum)
{
    if ((wizardWidget != NULL) && (wizardSubLayout != NULL) && (constraintDialog != NULL)) {
        wizardWidget = NULL;
        wizardSubLayout = NULL;
        delete constraintDialog;
        constraintDialog = NULL;

        // Notify the Shaft Wizard that we have finished editing
        // See WizardShaft.py on why we do it this way
        Gui::Command::runCommand(Gui::Command::Doc, "Gui.runCommand('PartDesign_WizardShaftCallBack')");
    } else {
        if (ModNum == ViewProvider::Default) {
            // when pressing ESC make sure to close the dialog
            Gui::Control().closeDialog();
        }
        else {
            ViewProviderDocumentObject::unsetEdit(ModNum);
        }
    }
}
/*
// Create a local coordinate system with the z-axis given in dir
void getLocalCoordinateSystem(const SbVec3f& z, SbVec3f& y, SbVec3f& x)
{
    // Find the y axis in an arbitrary direction, normal to z
    // Conditions:
    // y1 * z1 + y2 * z2 + y3 * z3 = |y| |z| cos(90°) = 0
    // |y| = sqrt(y1^2 + y2^2 + y3^2) = 1
    float z1, z2, z3;
    z.getValue(z1, z2, z3);
    float y1, y2, y3;
    if (fabs(z1) > Precision::Confusion()) {
        // Choose: y3 = 0
        // Solution:
        // y1 * z1 + y2 * z2 = 0
        // y1 = - z2/z1 y2
        // sqrt(z2^2/z1^2 y2^2 + y2^2) = 1
        // y2^2 ( 1 + z2^2/z1^2)) = +-1 -> choose +1 otherwise no solution
        // y2 = +- sqrt(1 / (1 + z2^2/z1^2))
        y3 = 0;
        y2 = sqrt(1 / (1 + z2*z2 / (z1*z1)));
        y1 = -z2/z1 * y2;
        // Note: result might be (0, 1, 0)
    } else if (fabs(z2) > Precision::Confusion()) {
        // Given: z1 = 0
        // Choose: y1 = 0
        // Solution:
        // y2 * z2 + y3 * z3 = 0
        // y2 = - z3/z2 y3
        // sqrt(z3^2/z2^2 y3^3 + y3^2) = 1
        // y3^2 (1 + z3^2/z2^2)) = +1
        // y3 = +- sqrt(1 / (1 + z3^2/z2^2))
        y1 = 0;
        y3 = sqrt(1 / (1 + z3*z3 / (z2*z2)));
        y2 = -z3/z2 * y3;
        // Note: result might be (0, 0, 1)
    } else if  (fabs(z3) > Precision::Confusion()) {
        // Given: z1 = z2 = 0
        // Choose the remaining possible axis
        y1 = 1;
        y2 = 0;
        y3 = 0;
    }

    y = SbVec3f(y1, y2, y3);
    x = y.cross(z);
}
*/
#define PLACEMENT_CHILDREN 2

void ViewProviderFemConstraint::createPlacement(SoSeparator* sep, const SbVec3f &base, const SbRotation &r)
{
    SoTranslation* trans = new SoTranslation();
    trans->translation.setValue(base);
    sep->addChild(trans);
    SoRotation* rot = new SoRotation();
    rot->rotation.setValue(r);
    sep->addChild(rot);
}

void ViewProviderFemConstraint::updatePlacement(const SoSeparator* sep, const int idx, const SbVec3f &base, const SbRotation &r)
{
    SoTranslation* trans = static_cast<SoTranslation*>(sep->getChild(idx));
    trans->translation.setValue(base);
    SoRotation* rot = static_cast<SoRotation*>(sep->getChild(idx+1));
    rot->rotation.setValue(r);
}

#define CONE_CHILDREN 2

void ViewProviderFemConstraint::createCone(SoSeparator* sep, const double height, const double radius)
{
    // Adjust cone so that the tip is on base
    SoTranslation* trans = new SoTranslation();
    trans->translation.setValue(SbVec3f(0,-height/2,0));
    sep->addChild(trans);
    SoCone* cone = new SoCone();
    cone->height.setValue(height);
    cone->bottomRadius.setValue(radius);
    sep->addChild(cone);
}

SoSeparator* ViewProviderFemConstraint::createCone(const double height, const double radius)
{
    // Create a new cone node
    SoSeparator* sep = new SoSeparator();
    createCone(sep, height, radius);
    return sep;
}

void ViewProviderFemConstraint::updateCone(const SoNode* node, const int idx, const double height, const double radius)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    SoTranslation* trans = static_cast<SoTranslation*>(sep->getChild(idx));
    trans->translation.setValue(SbVec3f(0,-height/2,0));
    SoCone* cone = static_cast<SoCone*>(sep->getChild(idx+1));
    cone->height.setValue(height);
    cone->bottomRadius.setValue(radius);
}

#define CYLINDER_CHILDREN 1

void ViewProviderFemConstraint::createCylinder(SoSeparator* sep, const double height, const double radius)
{
    SoCylinder* cyl = new SoCylinder();
    cyl->height.setValue(height);
    cyl->radius.setValue(radius);
    sep->addChild(cyl);
}

SoSeparator* ViewProviderFemConstraint::createCylinder(const double height, const double radius)
{
    // Create a new cylinder node
    SoSeparator* sep = new SoSeparator();
    createCylinder(sep, height, radius);
    return sep;
}

void ViewProviderFemConstraint::updateCylinder(const SoNode* node, const int idx, const double height, const double radius)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    SoCylinder* cyl = static_cast<SoCylinder*>(sep->getChild(idx));
    cyl->height.setValue(height);
    cyl->radius.setValue(radius);
}

#define CUBE_CHILDREN 1

void ViewProviderFemConstraint::createCube(SoSeparator* sep, const double width, const double length, const double height)
{
    SoCube* cube = new SoCube();
    cube->width.setValue(width);
    cube->depth.setValue(length);
    cube->height.setValue(height);
    sep->addChild(cube);
}

SoSeparator* ViewProviderFemConstraint::createCube(const double width, const double length, const double height)
{
    SoSeparator* sep = new SoSeparator();
    createCube(sep, width, length, height);
    return sep;
}

void ViewProviderFemConstraint::updateCube(const SoNode* node, const int idx, const double width, const double length, const double height)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    SoCube* cube = static_cast<SoCube*>(sep->getChild(idx));
    cube->width.setValue(width);
    cube->depth.setValue(length);
    cube->height.setValue(height);
}

#define ARROW_CHILDREN (CONE_CHILDREN + PLACEMENT_CHILDREN + CYLINDER_CHILDREN)

void ViewProviderFemConstraint::createArrow(SoSeparator* sep, const double length, const double radius)
{
    createCone(sep, radius, radius/2);
    createPlacement(sep, SbVec3f(0, -radius/2-(length-radius)/2, 0), SbRotation());
    createCylinder(sep, length-radius, radius/5);
}

SoSeparator* ViewProviderFemConstraint::createArrow(const double length, const double radius)
{
    SoSeparator* sep = new SoSeparator();
    createArrow(sep, length, radius);
    return sep;
}

void ViewProviderFemConstraint::updateArrow(const SoNode* node, const int idx, const double length, const double radius)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    updateCone(sep, idx, radius, radius/2);
    updatePlacement(sep, idx+CONE_CHILDREN, SbVec3f(0, -radius/2-(length-radius)/2, 0), SbRotation());
    updateCylinder(sep, idx+CONE_CHILDREN+PLACEMENT_CHILDREN, length-radius, radius/5);
}

#define FIXED_CHILDREN (CONE_CHILDREN + PLACEMENT_CHILDREN + CUBE_CHILDREN)

void ViewProviderFemConstraint::createFixed(SoSeparator* sep, const double height, const double width, const bool gap)
{
    createCone(sep, height-width/4, height-width/4);
    createPlacement(sep, SbVec3f(0, -(height-width/4)/2-width/8 - (gap ? 1.0 : 0.1) * width/8, 0), SbRotation());
    createCube(sep, width, width, width/4);
}

SoSeparator* ViewProviderFemConstraint::createFixed(const double height, const double width, const bool gap)
{
    SoSeparator* sep = new SoSeparator();
    createFixed(sep, height, width, gap);
    return sep;
}

void ViewProviderFemConstraint::updateFixed(const SoNode* node, const int idx, const double height, const double width, const bool gap)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    updateCone(sep, idx, height-width/4, height-width/4);
    updatePlacement(sep, idx+CONE_CHILDREN, SbVec3f(0, -(height-width/4)/2-width/8 - (gap ? 1.0 : 0.0) * width/8, 0), SbRotation());
    updateCube(sep, idx+CONE_CHILDREN+PLACEMENT_CHILDREN, width, width, width/4);
}

void ViewProviderFemConstraint::createDisplacement(SoSeparator* sep, const double height, const double width, const bool gap)
{
    createCone(sep, height, width);
    createPlacement(sep, SbVec3f(0, -(height)/2-width/8 - (gap ? 1.0 : 0.1) * width/8, 0), SbRotation());
}

SoSeparator* ViewProviderFemConstraint::createDisplacement(const double height, const double width, const bool gap)
{
    SoSeparator* sep = new SoSeparator();
    createDisplacement(sep, height, width, gap);
    return sep;
}

void ViewProviderFemConstraint::updateDisplacement(const SoNode* node, const int idx, const double height, const double width, const bool gap)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    updateCone(sep, idx, height, width);
    updatePlacement(sep, idx+CONE_CHILDREN, SbVec3f(0, -(height)/2-width/8 - (gap ? 1.0 : 0.0) * width/8, 0), SbRotation());
}

void ViewProviderFemConstraint::createRotation(SoSeparator* sep, const double height, const double width, const bool gap)
{
    createCylinder(sep, width/2, height/2);
    createPlacement(sep, SbVec3f(0, -(height)*2-width/8 - (gap ? 1.0 : 0.1) * width/8, 0), SbRotation());
}

SoSeparator* ViewProviderFemConstraint::createRotation(const double height, const double width, const bool gap)
{
    SoSeparator* sep = new SoSeparator();
    createRotation(sep, height, width, gap);
    return sep;
}

void ViewProviderFemConstraint::updateRotation(const SoNode* node, const int idx, const double height, const double width, const bool gap)
{
    const SoSeparator* sep = static_cast<const SoSeparator*>(node);
    updateCylinder(sep, idx, height/2, width/2);
    updatePlacement(sep, idx+CYLINDER_CHILDREN, SbVec3f(0, -(height)*2-width/8 - (gap ? 1.0 : 0.0) * width/8, 0), SbRotation());
}

QObject* ViewProviderFemConstraint::findChildByName(const QObject* parent, const QString& name)
{
    for (QObjectList::const_iterator o = parent->children().begin(); o != parent->children().end(); o++) {
        if ((*o)->objectName() == name)
            return *o;
        if (!(*o)->children().empty()) {
            QObject* result = findChildByName(*o, name);
            if (result != NULL)
                return result;
        }
    }

    return NULL;
}

void ViewProviderFemConstraint::checkForWizard()
{
    wizardWidget= NULL;
    wizardSubLayout = NULL;
    Gui::MainWindow* mw = Gui::getMainWindow();
    if (mw == NULL) return;
    QDockWidget* dw = mw->findChild<QDockWidget*>(QString::fromLatin1("Combo View"));
    if (dw == NULL) return;
    QWidget* cw = dw->findChild<QWidget*>(QString::fromLatin1("Combo View"));
    if (cw == NULL) return;
    QTabWidget* tw = cw->findChild<QTabWidget*>(QString::fromLatin1("combiTab"));
    if (tw == NULL) return;
    QStackedWidget* sw = tw->findChild<QStackedWidget*>(QString::fromLatin1("qt_tabwidget_stackedwidget"));
    if (sw == NULL) return;
    QScrollArea* sa = sw->findChild<QScrollArea*>();
    if (sa== NULL) return;
    QWidget* wd = sa->widget(); // This is the reason why we cannot use findChildByName() right away!!!
    if (wd == NULL) return;
    QObject* wiz = findChildByName(wd, QString::fromLatin1("ShaftWizard"));
    if (wiz != NULL) {
        wizardWidget = static_cast<QVBoxLayout*>(wiz);
        wizardSubLayout = wiz->findChild<QVBoxLayout*>(QString::fromLatin1("ShaftWizardLayout"));
    }
}
