/**
 * Function idf_export_module
 * retrieves information from all board modules, adds drill holes to
 * the DRILLED_HOLES or BOARD_OUTLINE section as appropriate,
 * compiles data for the PLACEMENT section and compiles data for
 * the library ELECTRICAL section.
 */
static void idf_export_module( BOARD* aPcb, MODULE* aModule,
        IDF3_BOARD& aIDFBoard )
{
    // Reference Designator
    std::string crefdes = TO_UTF8( aModule->GetReference() );

    if( crefdes.empty() || !crefdes.compare( "~" ) )
    {
        std::string cvalue = TO_UTF8( aModule->GetValue() );

        // if both the RefDes and Value are empty or set to '~' the board owns the part,
        // otherwise associated parts of the module must be marked NOREFDES.
        if( cvalue.empty() || !cvalue.compare( "~" ) )
            crefdes = "BOARD";
        else
            crefdes = "NOREFDES";
    }

    // TODO: If module cutouts are supported we must add code here
    // for( EDA_ITEM* item = aModule->GraphicalItems();  item != NULL;  item = item->Next() )
    // {
    // if( ( item->Type() != PCB_MODULE_EDGE_T )
    // || (item->GetLayer() != Edge_Cuts ) ) continue;
    // code to export cutouts
    // }

    // Export pads
    double  drill, x, y;
    double  scale = aIDFBoard.GetUserScale();
    IDF3::KEY_PLATING kplate;
    std::string pintype;
    std::string tstr;

    double dx, dy;

    aIDFBoard.GetUserOffset( dx, dy );

    for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
    {
        drill = (double) pad->GetDrillSize().x * scale;
        x     = pad->GetPosition().x * scale + dx;
        y     = -pad->GetPosition().y * scale + dy;

        // Export the hole on the edge layer
        if( drill > 0.0 )
        {
            // plating
            if( pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
                kplate = IDF3::NPTH;
            else
                kplate = IDF3::PTH;

            // hole type
            tstr = TO_UTF8( pad->GetPadName() );

            if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" )
                || ( kplate == IDF3::NPTH )
                ||( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ) )
                pintype = "MTG";
            else
                pintype = "PIN";

            // fields:
            // 1. hole dia. : float
            // 2. X coord : float
            // 3. Y coord : float
            // 4. plating : PTH | NPTH
            // 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"}
            // 6. type : PIN | VIA | MTG | TOOL | { "other" }
            // 7. owner : MCAD | ECAD | UNOWNED
            if( ( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
                && ( pad->GetDrillSize().x != pad->GetDrillSize().y ) )
            {
                // NOTE: IDF does not have direct support for slots;
                // slots are implemented as a board cutout and we
                // cannot represent plating or reference designators

                double dlength = pad->GetDrillSize().y * scale;

                // NOTE: The orientation of modules and pads have
                // the opposite sense due to KiCad drawing on a
                // screen with a LH coordinate system
                double angle = pad->GetOrientation() / 10.0;

                // NOTE: Since this code assumes the scenario where
                // GetDrillSize().y is the length but idf_parser.cpp
                // assumes a length along the X axis, the orientation
                // must be shifted +90 deg when GetDrillSize().y is
                // the major axis.

                if( dlength < drill )
                {
                    std::swap( drill, dlength );
                }
                else
                {
                    angle += 90.0;
                }

                // NOTE: KiCad measures a slot's length from end to end
                // rather than between the centers of the arcs
                dlength -= drill;

                aIDFBoard.AddSlot( drill, dlength, angle, x, y );
            }
            else
            {
                IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
                                                         pintype, IDF3::ECAD );

                if( !aIDFBoard.AddDrill( dp ) )
                {
                    delete dp;

                    std::ostringstream ostr;
                    ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
                    ostr << "(): could not add drill";

                    throw std::runtime_error( ostr.str() );
                }
            }
        }
    }

    // add any valid models to the library item list
    std::string refdes;

    IDF3_COMPONENT* comp = NULL;

    for( S3D_MASTER* modfile = aModule->Models(); modfile != 0; modfile = modfile->Next() )
    {
        if( !modfile->Is3DType( S3D_MASTER::FILE3D_IDF )
            || modfile->GetShape3DFullFilename().empty() )
            continue;

        if( refdes.empty() )
        {
            refdes = TO_UTF8( aModule->GetReference() );

            // NOREFDES cannot be used or else the software gets confused
            // when writing out the placement data due to conflicting
            // placement and layer specifications; to work around this we
            // create a (hopefully) unique refdes for our exported part.
            if( refdes.empty() || !refdes.compare( "~" ) )
                refdes = aIDFBoard.GetNewRefDes();
        }

        IDF3_COMP_OUTLINE* outline;

        outline = aIDFBoard.GetComponentOutline( modfile->GetShape3DFullFilename() );

        if( !outline )
            throw( std::runtime_error( aIDFBoard.GetError() ) );

        double rotz = aModule->GetOrientation()/10.0;
        double locx = modfile->m_MatPosition.x * 25.4;  // part offsets are in inches
        double locy = modfile->m_MatPosition.y * 25.4;
        double locz = modfile->m_MatPosition.z * 25.4;
        double lrot = modfile->m_MatRotation.z;

        bool top = ( aModule->GetLayer() == B_Cu ) ? false : true;

        if( top )
        {
            locy = -locy;
            RotatePoint( &locx, &locy, aModule->GetOrientation() );
            locy = -locy;
        }

        if( !top )
        {
            lrot = -lrot;
            RotatePoint( &locx, &locy, aModule->GetOrientation() );
            locy = -locy;

            rotz = 180.0 - rotz;

            if( rotz >= 360.0 )
                while( rotz >= 360.0 ) rotz -= 360.0;

            if( rotz <= -360.0 )
                while( rotz <= -360.0 ) rotz += 360.0;
        }

        if( comp == NULL )
            comp = aIDFBoard.FindComponent( refdes );

        if( comp == NULL )
        {
            comp = new IDF3_COMPONENT( &aIDFBoard );

            if( comp == NULL )
                throw( std::runtime_error( aIDFBoard.GetError() ) );

            comp->SetRefDes( refdes );

            if( top )
                comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                   -aModule->GetPosition().y * scale + dy,
                                   rotz, IDF3::LYR_TOP );
            else
                comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                   -aModule->GetPosition().y * scale + dy,
                                   rotz, IDF3::LYR_BOTTOM );

            comp->SetPlacement( IDF3::PS_ECAD );

            aIDFBoard.AddComponent( comp );
        }
        else
        {
            double refX, refY, refA;
            IDF3::IDF_LAYER side;

            if( ! comp->GetPosition( refX, refY, refA, side ) )
            {
                // place the item
                if( top )
                    comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                       -aModule->GetPosition().y * scale + dy,
                                       rotz, IDF3::LYR_TOP );
                else
                    comp->SetPosition( aModule->GetPosition().x * scale + dx,
                                       -aModule->GetPosition().y * scale + dy,
                                       rotz, IDF3::LYR_BOTTOM );

                comp->SetPlacement( IDF3::PS_ECAD );

            }
            else
            {
                // check that the retrieved component matches this one
                refX = refX - ( aModule->GetPosition().x * scale + dx );
                refY = refY - ( -aModule->GetPosition().y * scale + dy );
                refA = refA - rotz;
                refA *= refA;
                refX *= refX;
                refY *= refY;
                refX += refY;

                // conditions: same side, X,Y coordinates within 10 microns,
                // angle within 0.01 degree
                if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
                    || ( refA > 0.0001 ) || ( refX > 0.0001 ) )
                {
                    comp->GetPosition( refX, refY, refA, side );

                    std::ostringstream ostr;
                    ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
                    ostr << "* conflicting Reference Designator '" << refdes << "'\n";
                    ostr << "* X loc: " << (aModule->GetPosition().x * scale + dx);
                    ostr << " vs. " << refX << "\n";
                    ostr << "* Y loc: " << (-aModule->GetPosition().y * scale + dy);
                    ostr << " vs. " << refY << "\n";
                    ostr << "* angle: " << rotz;
                    ostr << " vs. " << refA << "\n";

                    if( top )
                        ostr << "* TOP vs. ";
                    else
                        ostr << "* BOTTOM vs. ";

                    if( side == IDF3::LYR_TOP )
                        ostr << "TOP";
                    else
                        ostr << "BOTTOM";

                    throw( std::runtime_error( ostr.str() ) );
                }
            }
        }


        // create the local data ...
        IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );

        data->SetOffsets( locx, locy, locz, lrot );
        comp->AddOutlineData( data );
    }

    return;
}
static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule,
        std::ofstream& aOutputFile,
        double aVRMLModelsToBiu,
        bool aExport3DFiles, const wxString& a3D_Subdir )
{
    // Reference and value
    if( aModule->Reference().IsVisible() )
        export_vrml_text_module( &aModule->Reference() );

    if( aModule->Value().IsVisible() )
        export_vrml_text_module( &aModule->Value() );

    // Export module edges
    for( EDA_ITEM* item = aModule->GraphicalItems(); item; item = item->Next() )
    {
        switch( item->Type() )
        {
        case PCB_MODULE_TEXT_T:
            export_vrml_text_module( static_cast<TEXTE_MODULE*>( item ) );
            break;

        case PCB_MODULE_EDGE_T:
            export_vrml_edge_module( aModel, static_cast<EDGE_MODULE*>( item ),
                                     aModule->GetOrientation() );
            break;

        default:
            break;
        }
    }

    // Export pads
    for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
        export_vrml_pad( aModel, aPcb, pad );

    bool isFlipped = aModule->GetLayer() == B_Cu;

    // Export the object VRML model(s)
    for( S3D_MASTER* vrmlm = aModule->Models();  vrmlm;  vrmlm = vrmlm->Next() )
    {
        if( !vrmlm->Is3DType( S3D_MASTER::FILE3D_VRML ) )
            continue;

        wxString fname = vrmlm->GetShape3DFullFilename();

        fname.Replace( wxT( "\\" ), wxT( "/" ) );
        wxString source_fname = fname;

        if( aExport3DFiles )
        {
            // Change illegal characters in filenames
            ChangeIllegalCharacters( fname, true );
            fname = a3D_Subdir + wxT( "/" ) + fname;

            if( !wxFileExists( fname ) )
                wxCopyFile( source_fname, fname );
        }

        /* Calculate 3D shape rotation:
         * this is the rotation parameters, with an additional 180 deg rotation
         * for footprints that are flipped
         * When flipped, axis rotation is the horizontal axis (X axis)
         */
        double rotx = -vrmlm->m_MatRotation.x;
        double roty = -vrmlm->m_MatRotation.y;
        double rotz = -vrmlm->m_MatRotation.z;

        if( isFlipped )
        {
            rotx += 180.0;
            NEGATE( roty );
            NEGATE( rotz );
        }

        // Do some quaternion munching
        double q1[4], q2[4], rot[4];
        build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 );
        build_quat( 0, 1, 0, DEG2RAD( roty ), q2 );
        compose_quat( q1, q2, q1 );
        build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 );
        compose_quat( q1, q2, q1 );

        // Note here aModule->GetOrientation() is in 0.1 degrees,
        // so module rotation has to be converted to radians
        build_quat( 0, 0, 1, DECIDEG2RAD( aModule->GetOrientation() ), q2 );
        compose_quat( q1, q2, q1 );
        from_quat( q1, rot );

        aOutputFile << "Transform {\n";

        // A null rotation would fail the acos!
        if( rot[3] != 0.0 )
        {
            aOutputFile << "  rotation " << std::setprecision( 3 );
            aOutputFile << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
        }

        // adjust 3D shape local offset position
        // they are given in inch, so they are converted in board IU.
        double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0;
        double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0;
        double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;

        if( isFlipped )
            NEGATE( offsetz );
        else // In normal mode, Y axis is reversed in Pcbnew.
            NEGATE( offsety );

        RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );

        aOutputFile << "  translation " << std::setprecision( aModel.precision );
        aOutputFile << (( offsetx + aModule->GetPosition().x) * aModel.scale + aModel.tx ) << " ";
        aOutputFile << ( -(offsety + aModule->GetPosition().y) * aModel.scale - aModel.ty ) << " ";
        aOutputFile << ( (offsetz * aModel.scale ) + aModel.GetLayerZ( aModule->GetLayer() ) ) << "\n";

        aOutputFile << "  scale ";
        aOutputFile << ( vrmlm->m_MatScale.x * aVRMLModelsToBiu ) << " ";
        aOutputFile << ( vrmlm->m_MatScale.y * aVRMLModelsToBiu ) << " ";
        aOutputFile << ( vrmlm->m_MatScale.z * aVRMLModelsToBiu ) << "\n";

        if( fname.EndsWith( wxT( "x3d" ) ) )
        {
            X3D_MODEL_PARSER* parser = new X3D_MODEL_PARSER( vrmlm );

            if( parser )
            {
                // embed x3d model in vrml format
                double vrml_to_x3d = aVRMLModelsToBiu;
                parser->Load( fname, vrml_to_x3d );

                try
                {
                    aOutputFile << "  children [\n ";
                    aOutputFile << TO_UTF8( parser->VRML2_representation() ) << " ]\n";
                    aOutputFile << "  }\n";
                }
                catch( const std::exception& e )
                {
                    delete parser;
                    throw;
                }
            }
        }
        else
        {
            aOutputFile << "  children [\n    Inline {\n      url \"";
            aOutputFile << TO_UTF8( fname ) << "\"\n    } ]\n";
            aOutputFile << "  }\n";
        }
    }
}
static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule,
                                std::ofstream& aOutputFile, double aVRMLModelsToBiu,
                                bool aExport3DFiles, bool aUseRelativePaths,
                                const wxString& a3D_Subdir )
{
    // Reference and value
    if( aModule->Reference().IsVisible() )
        export_vrml_text_module( &aModule->Reference() );

    if( aModule->Value().IsVisible() )
        export_vrml_text_module( &aModule->Value() );

    // Export module edges
    for( EDA_ITEM* item = aModule->GraphicalItems(); item; item = item->Next() )
    {
        switch( item->Type() )
        {
        case PCB_MODULE_TEXT_T:
            export_vrml_text_module( static_cast<TEXTE_MODULE*>( item ) );
            break;

        case PCB_MODULE_EDGE_T:
            export_vrml_edge_module( aModel, static_cast<EDGE_MODULE*>( item ),
                                     aModule->GetOrientation() );
            break;

        default:
            break;
        }
    }

    // Export pads
    for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
        export_vrml_pad( aModel, aPcb, pad );

    bool isFlipped = aModule->GetLayer() == B_Cu;

    // Export the object VRML model(s)
    for( S3D_MASTER* vrmlm = aModule->Models();  vrmlm;  vrmlm = vrmlm->Next() )
    {
        if( !vrmlm->Is3DType( S3D_MASTER::FILE3D_VRML ) )
            continue;

        wxFileName modelFileName = vrmlm->GetShape3DFullFilename();
        wxFileName destFileName( a3D_Subdir, modelFileName.GetName(), modelFileName.GetExt() );

        // Only copy VRML files.
        if( modelFileName.FileExists() && modelFileName.GetExt() == wxT( "wrl" ) )
        {
            if( aExport3DFiles )
            {
                wxDateTime srcModTime = modelFileName.GetModificationTime();
                wxDateTime destModTime = srcModTime;

                destModTime.SetToCurrent();

                if( destFileName.FileExists() )
                    destModTime = destFileName.GetModificationTime();

                // Only copy the file if it doesn't exist or has been modified.  This eliminates
                // the redundant file copies.
                if( srcModTime != destModTime )
                {
                    wxLogDebug( wxT( "Copying 3D model %s to %s." ),
                                GetChars( modelFileName.GetFullPath() ),
                                GetChars( destFileName.GetFullPath() ) );

                    if( !wxCopyFile( modelFileName.GetFullPath(), destFileName.GetFullPath() ) )
                        continue;
                }
            }

            /* Calculate 3D shape rotation:
             * this is the rotation parameters, with an additional 180 deg rotation
             * for footprints that are flipped
             * When flipped, axis rotation is the horizontal axis (X axis)
             */
            double rotx = -vrmlm->m_MatRotation.x;
            double roty = -vrmlm->m_MatRotation.y;
            double rotz = -vrmlm->m_MatRotation.z;

            if( isFlipped )
            {
                rotx += 180.0;
                NEGATE( roty );
                NEGATE( rotz );
            }

            // Do some quaternion munching
            double q1[4], q2[4], rot[4];
            build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 );
            build_quat( 0, 1, 0, DEG2RAD( roty ), q2 );
            compose_quat( q1, q2, q1 );
            build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 );
            compose_quat( q1, q2, q1 );

            // Note here aModule->GetOrientation() is in 0.1 degrees,
            // so module rotation has to be converted to radians
            build_quat( 0, 0, 1, DECIDEG2RAD( aModule->GetOrientation() ), q2 );
            compose_quat( q1, q2, q1 );
            from_quat( q1, rot );

            aOutputFile << "Transform {\n";

            // A null rotation would fail the acos!
            if( rot[3] != 0.0 )
            {
                aOutputFile << "  rotation " << std::setprecision( 3 );
                aOutputFile << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
            }

            // adjust 3D shape local offset position
            // they are given in inch, so they are converted in board IU.
            double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0;
            double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0;
            double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;

            if( isFlipped )
                NEGATE( offsetz );
            else // In normal mode, Y axis is reversed in Pcbnew.
                NEGATE( offsety );

            RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );

            aOutputFile << "  translation " << std::setprecision( aModel.precision );
            aOutputFile << ( ( offsetx + aModule->GetPosition().x ) *
                             aModel.scale + aModel.tx ) << " ";
            aOutputFile << ( -(offsety + aModule->GetPosition().y) *
                             aModel.scale - aModel.ty ) << " ";
            aOutputFile << ( (offsetz * aModel.scale ) +
                             aModel.GetLayerZ( aModule->GetLayer() ) ) << "\n";
            aOutputFile << "  scale ";
            aOutputFile << ( vrmlm->m_MatScale.x * aVRMLModelsToBiu ) << " ";
            aOutputFile << ( vrmlm->m_MatScale.y * aVRMLModelsToBiu ) << " ";
            aOutputFile << ( vrmlm->m_MatScale.z * aVRMLModelsToBiu ) << "\n";
            aOutputFile << "  children [\n    Inline {\n      url \"";

            if( aUseRelativePaths )
            {
                wxFileName tmp = destFileName;
                tmp.SetExt( wxT( "" ) );
                tmp.SetName( wxT( "" ) );
                tmp.RemoveLastDir();
                destFileName.MakeRelativeTo( tmp.GetPath() );
            }

            wxString fn = destFileName.GetFullPath();
            fn.Replace( wxT( "\\" ), wxT( "/" ) );
            aOutputFile << TO_UTF8( fn ) << "\"\n    } ]\n";
            aOutputFile << "  }\n";
        }
    }
}