/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2015-2017 Cirilo Bernardo <cirilo.bernardo@gmail.com>
 * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file sg_helpers.h
 *
 * Define a number of macros to aid in repetitious code which is probably best expressed
 * as a preprocessor macro rather than as a template. This header also declares a number
 * of functions which are only of use within the sg_* classes.
 */

#ifndef SG_HELPERS_H
#define SG_HELPERS_H

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include "plugins/3dapi/sg_base.h"
#include "plugins/3dapi/sg_types.h"
#include <glm/glm.hpp>

class SGNORMALS;
class SGCOORDS;
class SGCOORDINDEX;


// Function to drop references within an SGNODE
// The node being destroyed must remove itself from the object reference's
// backpointer list in order to avoid a segfault.
#define DROP_REFS( aType, aList )                         \
    do                                                    \
    {                                                     \
        std::vector<aType*>::iterator sL = aList.begin(); \
        std::vector<aType*>::iterator eL = aList.end();   \
        while( sL != eL )                                 \
        {                                                 \
            ( (SGNODE*) *sL )->delNodeRef( this );        \
            ++sL;                                         \
        }                                                 \
        aList.clear();                                    \
    } while( 0 )


// Function to delete owned objects within an SGNODE
// The owned object's parent is set to NULL before
// deletion to avoid a redundant 'unlinkChildNode' call.
#define DEL_OBJS( aType, aList )                             \
    do                                                       \
    {                                                        \
        std::vector<aType*>::iterator sL = aList.begin();    \
        std::vector<aType*>::iterator eL = aList.end();      \
        while( sL != eL )                                    \
        {                                                    \
            ( (SGNODE*) *sL )->SetParent( nullptr, false );  \
            delete *sL;                                      \
            ++sL;                                            \
        }                                                    \
        aList.clear();                                       \
    } while( 0 )


// Function to unlink a child or reference node when that child or
// reference node is being destroyed.
#define UNLINK_NODE( aNodeID, aType, aNode, aOwnedList, aRefList, isChild ) \
    do                                                                      \
    {                                                                       \
        if( aNodeID == aNode->GetNodeType() )                               \
        {                                                                   \
            std::vector<aType*>*          oSL;                              \
            std::vector<aType*>::iterator sL;                               \
            std::vector<aType*>::iterator eL;                               \
            if( isChild )                                                   \
            {                                                               \
                oSL = &aOwnedList;                                          \
                sL  = oSL->begin();                                         \
                eL  = oSL->end();                                           \
                while( sL != eL )                                           \
                {                                                           \
                    if( (SGNODE*) *sL == aNode )                            \
                    {                                                       \
                        oSL->erase( sL );                                   \
                        return;                                             \
                    }                                                       \
                    ++sL;                                                   \
                }                                                           \
            }                                                               \
            else                                                            \
            {                                                               \
                oSL = &aRefList;                                            \
                sL  = oSL->begin();                                         \
                eL  = oSL->end();                                           \
                while( sL != eL )                                           \
                {                                                           \
                    if( (SGNODE*) *sL == aNode )                            \
                    {                                                       \
                        delNodeRef( this );                                 \
                        oSL->erase( sL );                                   \
                        return;                                             \
                    }                                                       \
                    ++sL;                                                   \
                }                                                           \
            }                                                               \
            return;                                                         \
        }                                                                   \
    } while( 0 )


// Function to check a node type, check for an existing reference,
// and add the node type to the reference list if applicable
#define ADD_NODE( aNodeID, aType, aNode, aOwnedList, aRefList, isChild )                           \
    do                                                                                             \
    {                                                                                              \
        if( aNodeID == aNode->GetNodeType() )                                                      \
        {                                                                                          \
            std::vector<aType*>::iterator sL;                                                      \
            sL = std::find( aOwnedList.begin(), aOwnedList.end(), aNode );                         \
            if( sL != aOwnedList.end() )                                                           \
                return true;                                                                       \
            sL = std::find( aRefList.begin(), aRefList.end(), aNode );                             \
            if( sL != aRefList.end() )                                                             \
                return true;                                                                       \
            if( isChild )                                                                          \
            {                                                                                      \
                SGNODE* ppn = (SGNODE*) aNode->GetParent();                                        \
                if( nullptr != ppn )                                                               \
                {                                                                                  \
                    if( this != ppn )                                                              \
                    {                                                                              \
                        std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; \
                        std::cerr << " * [BUG] object '" << aNode->GetName();                      \
                        std::cerr << "' has multiple parents '" << ppn->GetName() << "', '";       \
                        std::cerr << m_Name << "'\n";                                              \
                        return false;                                                              \
                    }                                                                              \
                }                                                                                  \
                aOwnedList.push_back( (aType*) aNode );                                            \
                aNode->SetParent( this, false );                                                   \
            }                                                                                      \
            else                                                                                   \
            {                                                                                      \
                /*if( nullptr == aNode->GetParent() ) { \
                std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n"; \
                std::cerr << " * [BUG] object '" << aNode->GetName(); \
                std::cerr << "' has no parent\n"; \
                std::cerr << " * [INFO] possible copy assignment or copy constructor bug\n"; \
                return false; \
            } */                                             \
                aRefList.push_back( (aType*) aNode );                                              \
                aNode->addNodeRef( this );                                                         \
            }                                                                                      \
            return true;                                                                           \
        }                                                                                          \
    } while( 0 )


// Function to find a node object given a (non-unique) node name
#define FIND_NODE( aType, aName, aNodeList, aCallingNode )         \
    do                                                             \
    {                                                              \
        std::vector<aType*>::iterator sLA = aNodeList.begin();     \
        std::vector<aType*>::iterator eLA = aNodeList.end();       \
        SGNODE*                       psg = nullptr;               \
        while( sLA != eLA )                                        \
        {                                                          \
            if( (SGNODE*) *sLA != aCallingNode )                   \
            {                                                      \
                psg = (SGNODE*) ( *sLA )->FindNode( aName, this ); \
                if( nullptr != psg )                               \
                    return psg;                                    \
            }                                                      \
            ++sLA;                                                 \
        }                                                          \
    } while( 0 )


namespace S3D
{
    bool degenerate( glm::dvec3* pts ) noexcept;

    //
    // Normals calculations from triangles
    //

    /*
     * Take an array of 3D coordinates and its corresponding index set and calculates
     * the normals assuming that indices are given in CCW order.
     *
     * Care must be taken in using this function to ensure that:
     *  -# All coordinates are indexed; unindexed coordinates are assigned normal(0,0,1);
     *     when dealing with VRML models which may list and reuse one large coordinate set it
     *     is necessary to gather all index sets and perform this operation only once.
     *  -# Index sets must represent triangles (multiple of 3 indices) and must not be
     *     degenerate, that is all indices and coordinates in a triad must be unique.
     *
     * @param coords is the array of 3D vertices.
     * @param index is the array of 3x vertex indices (triads).
     * @param norms is an empty array which holds the normals corresponding to each vector.
     * @return true on success; otherwise false.
     */
    bool CalcTriangleNormals( std::vector< SGPOINT > coords, std::vector< int >& index,
                              std::vector< SGVECTOR >& norms );

    // formats a floating point number for text output to a VRML file
    void FormatFloat( std::string& result, double value );

    // format orientation data for VRML output
    void FormatOrientation( std::string& result, const SGVECTOR& axis, double rotation );

    // format point data for VRML output
    void FormatPoint( std::string& result, const SGPOINT& point );

    // format vector data for VRML output
    void FormatVector( std::string& result, const SGVECTOR& aVector );

    // format Color data for VRML output
    void FormatColor( std::string& result, const SGCOLOR& aColor );

    //
    // Cache related WRITE functions
    //

    // write out an XYZ vertex
    bool WritePoint( std::ostream& aFile, const SGPOINT& aPoint );

    // write out a unit vector
    bool WriteVector( std::ostream& aFile, const SGVECTOR& aVector );

    // write out an RGB color
    bool WriteColor( std::ostream& aFile, const SGCOLOR& aColor );

    /**
     * Read the text tag of a binary cache file which is the NodeTag and unique ID number combined.
     *
     * @param aFile is a binary file open for reading.
     * @param aName will hold the tag name on successful return.
     * @return will be the NodeType which the tag represents or S3D::SGTYPES::SGTYPE_END on
     *         failure.
     */
    S3D::SGTYPES ReadTag( std::istream& aFile, std::string& aName );

    // read an XYZ vertex
    bool ReadPoint( std::istream& aFile, SGPOINT& aPoint );

    // read a unit vector
    bool ReadVector( std::istream& aFile, SGVECTOR& aVector );

    // read an RGB color
    bool ReadColor( std::istream& aFile, SGCOLOR& aColor );
}

#endif  // SG_HELPERS_H