// brief Get a bounding box of a PBLOB.
// TODO(mezhirov) delete this function and replace with blob->bounding_box()
static TBOX pblob_get_bbox(PBLOB *blob) {
  OUTLINE_LIST *outlines = blob->out_list();
  OUTLINE_IT it(outlines);
  TBOX result;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    OUTLINE *outline = it.data();
    outline->compute_bb();
    result.bounding_union(outline->bounding_box());
  }
  return result;
}
TBOX PBLOB::bounding_box() {  //bounding box
  OUTLINE *outline;              //current outline
  OUTLINE_IT it = &outlines;     //outlines of blob
  TBOX box;                       //bounding box

  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
    outline = it.data ();
    box += outline->bounding_box ();
  }
  return box;
}
float PBLOB::area() {  //area
  OUTLINE *outline;              //current outline
  OUTLINE_IT it = &outlines;     //outlines of blob
  float total;                   //total area

  total = 0.0f;
  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
    outline = it.data ();
    total += outline->area ();
  }
  return total;
}
TESSLINE *make_tess_outlines(                            //make tess outlines
                             OUTLINE_LIST *outlinelist,  //list to convert
                             BOOL8 flatten               //flatten outline structure
                            ) {
  OUTLINE_IT it = outlinelist;   //iterator
  OUTLINE *outline;              //current outline
  TESSLINE *head;                //output list
  TESSLINE *tail;                //end of list
  TESSLINE *tessoutline;

  head = NULL;
  tail = NULL;
  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
    outline = it.data ();
    tessoutline = newoutline ();
    tessoutline->compactloop = NULL;
    tessoutline->loop = make_tess_edgepts (outline->polypts (),
      tessoutline->topleft,
      tessoutline->botright);
    if (tessoutline->loop == NULL) {
      oldoutline(tessoutline);
      continue;
    }
    tessoutline->start = tessoutline->loop->pos;
    tessoutline->node = NULL;
    tessoutline->next = NULL;
    tessoutline->child = NULL;
    if (!outline->child ()->empty ()) {
      if (flatten)
        tessoutline->next = (struct olinestruct *)
          make_tess_outlines (outline->child (), flatten);
      else {
        tessoutline->next = NULL;
        tessoutline->child = (struct olinestruct *)
          make_tess_outlines (outline->child (), flatten);
      }
    }
    else
      tessoutline->next = NULL;
    if (head)
      tail->next = tessoutline;
    else
      head = tessoutline;
    while (tessoutline->next != NULL)
      tessoutline = tessoutline->next;
    tail = tessoutline;
  }
  return head;
}
BOOL8
OUTLINE::operator< (             //winding number
OUTLINE & other                  //other outline
) {
  inT16 count;                   //winding count
  POLYPT_IT it = &outline;       //iterator

  if (!box.overlap (other.box))
    return FALSE;                //can't be contained

  do {
    count = other.winding_number (FCOORD (it.data ()->pos));
    //get winding number
    if (count != INTERSECTING)
      return count != 0;
    it.forward ();
  }
  while (!it.at_first ());

                                 //switch lists
  it.set_to_list (&other.outline);
  do {
                                 //try other way round
    count = winding_number (FCOORD (it.data ()->pos));
    if (count != INTERSECTING)
      return count == 0;
    it.forward ();
  }
  while (!it.at_first ());
  return TRUE;
}
static void plot_outline_list(                     //draw outlines
                              OUTLINE_LIST *list,  //outline to draw
                              ScrollView* window,       //window to draw in
                              ScrollView::Color colour,       //colour to use
                              ScrollView::Color child_colour  //colour of children
                             ) {
  OUTLINE *outline;              //current outline
  OUTLINE_IT it = list;          //iterator

  for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
    outline = it.data ();
                                 //draw it
    outline->plot (window, colour);
    if (!outline->child ()->empty ())
      plot_outline_list (outline->child (), window,
        child_colour, child_colour);
  }
}
// create the PCB (board only) model using the current outlines and drill holes
bool PCBMODEL::CreatePCB()
{
    if( m_hasPCB )
    {
        if( m_pcb_label.IsNull() )
            return false;

        return true;
    }

    if( m_curves.empty() || m_mincurve == m_curves.end() )
    {
        m_hasPCB = true;
        std::ostringstream ostr;
        ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
        ostr << "  * no valid board outline\n";
        wxLogMessage( "%s\n", ostr.str().c_str() );
        return false;
    }

    m_hasPCB = true;    // whether or not operations fail we note that CreatePCB has been invoked
    TopoDS_Shape board;
    OUTLINE oln;    // loop to assemble (represents PCB outline and cutouts)
    oln.AddSegment( *m_mincurve );
    m_curves.erase( m_mincurve );

    while( !m_curves.empty() )
    {
        if( oln.IsClosed() )
        {
            if( board.IsNull() )
            {
                if( !oln.MakeShape( board, m_thickness ) )
                {
                    std::ostringstream ostr;
                    ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
                    ostr << "  * could not create board extrusion\n";
                    wxLogMessage( "%s\n", ostr.str().c_str() );

                    return false;
                }
            }
            else
            {
                TopoDS_Shape hole;

                if( oln.MakeShape( hole, m_thickness ) )
                {
                    m_cutouts.push_back( hole );
                }
                else
                {
                    std::ostringstream ostr;
                    ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
                    ostr << "  * could not create board cutout\n";
                    wxLogMessage( "%s\n", ostr.str().c_str() );
                }
            }

            oln.Clear();

            if( !m_curves.empty() )
            {
                oln.AddSegment( m_curves.front() );
                m_curves.pop_front();
            }

            continue;
        }

        std::list< KICADCURVE >::iterator sC = m_curves.begin();
        std::list< KICADCURVE >::iterator eC = m_curves.end();

        while( sC != eC )
        {
            if( oln.AddSegment( *sC ) )
            {
                m_curves.erase( sC );
                break;
            }

            ++sC;
        }

        if( sC == eC && !oln.m_curves.empty() )
        {
            std::ostringstream ostr;
            ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
            ostr << "  * could not close outline (dropping outline data with " << oln.m_curves.size() << " segments)\n";
            wxLogMessage( "%s\n", ostr.str().c_str() );
            oln.Clear();

            if( !m_curves.empty() )
            {
                oln.AddSegment( m_curves.front() );
                m_curves.pop_front();
            }
        }
    }

    if( oln.IsClosed() )
    {
        if( board.IsNull() )
        {
            if( !oln.MakeShape( board, m_thickness ) )
            {
                std::ostringstream ostr;
                ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
                ostr << "  * could not create board extrusion\n";
                wxLogMessage( "%s\n", ostr.str().c_str() );
                return false;
            }
        }
        else
        {
            TopoDS_Shape hole;

            if( oln.MakeShape( hole, m_thickness ) )
            {
                m_cutouts.push_back( hole );
            }
            else
            {
                std::ostringstream ostr;
                ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
                ostr << "  * could not create board cutout\n";
                wxLogMessage( "%s\n", ostr.str().c_str() );
            }
        }
    }

    // subtract cutouts (if any)
    for( auto i : m_cutouts )
        board = BRepAlgoAPI_Cut( board, i );

    // push the board to the data structure
    m_pcb_label = m_assy->AddComponent( m_assy_label, board );

    if( m_pcb_label.IsNull() )
        return false;

    // color the PCB
    Handle(XCAFDoc_ColorTool) color =
        XCAFDoc_DocumentTool::ColorTool( m_doc->Main () );
    Quantity_Color pcb_green( 0.06, 0.4, 0.06, Quantity_TOC_RGB );
    color->SetColor( m_pcb_label, pcb_green, XCAFDoc_ColorSurf );

    TopExp_Explorer topex;
    topex.Init( m_assy->GetShape( m_pcb_label ), TopAbs_SOLID );

    while( topex.More() )
    {
        color->SetColor( topex.Current(), pcb_green, XCAFDoc_ColorSurf );
        topex.Next();
    }

    return true;
}
// add a pad hole or slot
bool PCBMODEL::AddPadHole( KICADPAD* aPad )
{
    if( NULL == aPad || !aPad->IsThruHole() )
        return false;

    if( !aPad->m_drill.oval )
    {
        TopoDS_Shape s = BRepPrimAPI_MakeCylinder( aPad->m_drill.size.x * 0.5,
            m_thickness * 2.0 ).Shape();
        gp_Trsf shift;
        shift.SetTranslation( gp_Vec( aPad->m_position.x, aPad->m_position.y, -m_thickness * 0.5 ) );
        BRepBuilderAPI_Transform hole( s, shift );
        m_cutouts.push_back( hole.Shape() );
        return true;
    }

    // slotted hole
    double angle_offset = 0.0;
    double rad;     // radius of the slot
    double hlen;    // half length of the slot

    if( aPad->m_drill.size.x < aPad->m_drill.size.y )
    {
        angle_offset = M_PI_2;
        rad = aPad->m_drill.size.x * 0.5;
        hlen = aPad->m_drill.size.y * 0.5 - rad;
    }
    else
    {
        rad = aPad->m_drill.size.y * 0.5;
        hlen = aPad->m_drill.size.x * 0.5 - rad;
    }

    DOUBLET c0( -hlen, 0.0 );
    DOUBLET c1( hlen, 0.0 );
    DOUBLET p0( -hlen, rad );
    DOUBLET p1( -hlen, -rad );
    DOUBLET p2(  hlen, -rad );
    DOUBLET p3( hlen, rad );

    angle_offset += aPad->m_rotation;
    double dlim = (double)std::numeric_limits< float >::epsilon();

    if( angle_offset < -dlim || angle_offset > dlim )
    {
        double vsin = sin( angle_offset );
        double vcos = cos( angle_offset );

        double x = c0.x * vcos - c0.y * vsin;
        double y = c0.x * vsin + c0.y * vcos;
        c0.x = x;
        c0.y = y;

        x = c1.x * vcos - c1.y * vsin;
        y = c1.x * vsin + c1.y * vcos;
        c1.x = x;
        c1.y = y;

        x = p0.x * vcos - p0.y * vsin;
        y = p0.x * vsin + p0.y * vcos;
        p0.x = x;
        p0.y = y;

        x = p1.x * vcos - p1.y * vsin;
        y = p1.x * vsin + p1.y * vcos;
        p1.x = x;
        p1.y = y;

        x = p2.x * vcos - p2.y * vsin;
        y = p2.x * vsin + p2.y * vcos;
        p2.x = x;
        p2.y = y;

        x = p3.x * vcos - p3.y * vsin;
        y = p3.x * vsin + p3.y * vcos;
        p3.x = x;
        p3.y = y;
    }

    c0.x += aPad->m_position.x;
    c0.y += aPad->m_position.y;
    c1.x += aPad->m_position.x;
    c1.y += aPad->m_position.y;
    p0.x += aPad->m_position.x;
    p0.y += aPad->m_position.y;
    p1.x += aPad->m_position.x;
    p1.y += aPad->m_position.y;
    p2.x += aPad->m_position.x;
    p2.y += aPad->m_position.y;
    p3.x += aPad->m_position.x;
    p3.y += aPad->m_position.y;

    OUTLINE oln;
    KICADCURVE crv0, crv1, crv2, crv3;

    // crv0 = arc
    crv0.m_start = c0;
    crv0.m_end = p0;
    crv0.m_ep = p1;
    crv0.m_angle = M_PI;
    crv0.m_radius = rad;
    crv0.m_form = CURVE_ARC;

    // crv1 = line
    crv1.m_start = p1;
    crv1.m_end = p2;
    crv1.m_form = CURVE_LINE;

    // crv2 = arc
    crv2.m_start = c1;
    crv2.m_end = p2;
    crv2.m_ep = p3;
    crv2.m_angle = M_PI;
    crv2.m_radius = rad;
    crv2.m_form = CURVE_ARC;

    // crv3 = line
    crv3.m_start = p3;
    crv3.m_end = p0;
    crv3.m_form = CURVE_LINE;

    oln.AddSegment( crv0 );
    oln.AddSegment( crv1 );
    oln.AddSegment( crv2 );
    oln.AddSegment( crv3 );
    TopoDS_Shape slot;

    if( oln.MakeShape( slot, m_thickness ) )
    {
        if( !slot.IsNull() )
            m_cutouts.push_back( slot );

        return true;
    }

    return false;
}