/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013-2017 CERN
 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * @author Maciej Suminski <maciej.suminski@cern.ch>
 *
 * 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
 */


#include <eda_item.h>
#include <layer_ids.h>
#include <trace_helpers.h>

#include <view/view.h>
#include <view/view_group.h>
#include <view/view_item.h>
#include <view/view_rtree.h>
#include <view/view_overlay.h>

#include <gal/definitions.h>
#include <gal/graphics_abstraction_layer.h>
#include <painter.h>

#include <profile.h>

#ifdef KICAD_GAL_PROFILE
#include <wx/log.h>
#endif

namespace KIGFX {

class VIEW;

class VIEW_ITEM_DATA
{
public:
    VIEW_ITEM_DATA() :
        m_view( nullptr ),
        m_flags( KIGFX::VISIBLE ),
        m_requiredUpdate( KIGFX::NONE ),
        m_drawPriority( 0 ),
        m_groups( nullptr ),
        m_groupsSize( 0 ) {}

    ~VIEW_ITEM_DATA()
    {
        deleteGroups();
    }

    int GetFlags() const
    {
        return m_flags;
    }

private:
    friend class VIEW;

    /**
     * Return layer numbers used by the item.
     *
     * @param aLayers[]: output layer index array
     * @param aCount: number of layer indices in aLayers[]
     */
    void getLayers( int* aLayers, int& aCount ) const
    {
        int* layersPtr = aLayers;

        for( int layer : m_layers )
            *layersPtr++ = layer;

        aCount = m_layers.size();
    }

    /**
     * Return number of the group id for the given layer, or -1 in case it was not cached before.
     *
     * @param aLayer is the layer number for which group id is queried.
     * @return group id or -1 in case there is no group id (ie. item is not cached).
     */
    int getGroup( int aLayer ) const
    {
        for( int i = 0; i < m_groupsSize; ++i )
        {
            if( m_groups[i].first == aLayer )
                return m_groups[i].second;
        }

        return -1;
    }

    /**
     * Set a group id for the item and the layer combination.
     *
     * @param aLayer is the layer number.
     * @param aGroup is the group id.
     */
    void setGroup( int aLayer, int aGroup )
    {
        // Look if there is already an entry for the layer
        for( int i = 0; i < m_groupsSize; ++i )
        {
            if( m_groups[i].first == aLayer )
            {
                m_groups[i].second = aGroup;
                return;
            }
        }

        // If there was no entry for the given layer - create one
        std::pair<int, int>* newGroups = new std::pair<int, int>[m_groupsSize + 1];

        if( m_groupsSize > 0 )
        {
            std::copy( m_groups, m_groups + m_groupsSize, newGroups );
            delete[] m_groups;
        }

        m_groups = newGroups;
        newGroups[m_groupsSize++] = { aLayer, aGroup };
    }


    /**
     * Remove all of the stored group ids. Forces recaching of the item.
     */
    void deleteGroups()
    {
        delete[] m_groups;
        m_groups = nullptr;
        m_groupsSize = 0;
    }


    /**
     * Return information if the item uses at least one group id (ie. if it is cached at all).
     *
     * @returns true in case it is cached at least for one layer.
     */
    inline bool storesGroups() const
    {
        return m_groupsSize > 0;
    }


    /**
     * Reorder the stored groups (to facilitate reordering of layers).
     *
     * @see VIEW::ReorderLayerData
     *
     * @param aReorderMap is the mapping of old to new layer ids
     */
    void reorderGroups( std::unordered_map<int, int> aReorderMap )
    {
        for( int i = 0; i < m_groupsSize; ++i )
        {
            int orig_layer = m_groups[i].first;
            int new_layer = orig_layer;

            try
            {
                new_layer = aReorderMap.at( orig_layer );
            }
            catch( const std::out_of_range& )
            {}

            m_groups[i].first = new_layer;
        }
    }

    /**
     * Save layers used by the item.
     *
     * @param aLayers is an array containing layer numbers to be saved.
     * @param aCount is the size of the array.
     */
    void saveLayers( int* aLayers, int aCount )
    {
        m_layers.clear();

        for( int i = 0; i < aCount; ++i )
        {
            // this fires on some eagle board after EAGLE_PLUGIN::Load()
            wxASSERT( unsigned( aLayers[i] ) <= unsigned( VIEW::VIEW_MAX_LAYERS ) );

            m_layers.push_back( aLayers[i] );
        }
    }

    /**
     * Return current update flag for an item.
     */
    int requiredUpdate() const
    {
        return m_requiredUpdate;
    }

    /**
     * Mark an item as already updated, so it is not going to be redrawn.
     */
    void clearUpdateFlags()
    {
        m_requiredUpdate = NONE;
    }

    /**
     * Return if the item should be drawn or not.
     */
    bool isRenderable() const
    {
        return m_flags == VISIBLE;
    }

    VIEW*                m_view;             ///< Current dynamic view the item is assigned to.
    int                  m_flags;            ///< Visibility flags
    int                  m_requiredUpdate;   ///< Flag required for updating
    int                  m_drawPriority;     ///< Order to draw this item in a layer, lowest first

    std::pair<int, int>* m_groups;           ///< layer_number:group_id pairs for each layer the
                                             ///< item occupies.
    int                  m_groupsSize;

    std::vector<int>     m_layers;           /// Stores layer numbers used by the item.
};


void VIEW::OnDestroy( VIEW_ITEM* aItem )
{
    VIEW_ITEM_DATA* data = aItem->viewPrivData();

    if( !data )
        return;

    if( data->m_view )
        data->m_view->VIEW::Remove( aItem );

    delete data;
    aItem->ClearViewPrivData();
}


VIEW::VIEW( bool aIsDynamic ) :
    m_enableOrderModifier( true ),
    m_scale( 4.0 ),
    m_minScale( 0.2 ), m_maxScale( 50000.0 ),
    m_mirrorX( false ), m_mirrorY( false ),
    m_painter( nullptr ),
    m_gal( nullptr ),
    m_dynamic( aIsDynamic ),
    m_useDrawPriority( false ),
    m_nextDrawPriority( 0 ),
    m_reverseDrawOrder( false )
{
    // Set m_boundary to define the max area size. The default area size
    // is defined here as the max value of a int.
    // this is a default value acceptable for Pcbnew and Gerbview, but too large for Eeschema.
    // So in eeschema a call to SetBoundary() with a smaller value will be needed.
    typedef std::numeric_limits<int> coord_limits;
    double pos = coord_limits::lowest() / 2 + coord_limits::epsilon();
    double size = coord_limits::max() - coord_limits::epsilon();
    m_boundary.SetOrigin( pos, pos );
    m_boundary.SetSize( size, size );

    m_allItems.reset( new std::vector<VIEW_ITEM*> );
    m_allItems->reserve( 32768 );

    // Redraw everything at the beginning
    MarkDirty();

    m_layers.reserve( VIEW_MAX_LAYERS );

    // View uses layers to display EDA_ITEMs (item may be displayed on several layers, for example
    // pad may be shown on pad, pad hole and solder paste layers). There are usual copper layers
    // (eg. F.Cu, B.Cu, internal and so on) and layers for displaying objects such as texts,
    // silkscreen, pads, vias, etc.
    for( int ii = 0; ii < VIEW_MAX_LAYERS; ++ii )
    {
        m_layers.emplace_back();
        m_layers[ii].items          = std::make_shared<VIEW_RTREE>();
        m_layers[ii].id             = ii;
        m_layers[ii].renderingOrder = ii;
        m_layers[ii].visible        = true;
        m_layers[ii].displayOnly    = false;
        m_layers[ii].diffLayer      = false;
        m_layers[ii].hasNegatives   = false;
        m_layers[ii].target         = TARGET_CACHED;
    }

    sortLayers();

    m_preview.reset( new KIGFX::VIEW_GROUP() );
    Add( m_preview.get() );
}


VIEW::~VIEW()
{
    Remove( m_preview.get() );
}


void VIEW::Add( VIEW_ITEM* aItem, int aDrawPriority )
{
    int layers[VIEW_MAX_LAYERS], layers_count;

    if( aDrawPriority < 0 )
        aDrawPriority = m_nextDrawPriority++;

    if( !aItem->m_viewPrivData )
        aItem->m_viewPrivData = new VIEW_ITEM_DATA;

    aItem->m_viewPrivData->m_view = this;
    aItem->m_viewPrivData->m_drawPriority = aDrawPriority;

    aItem->ViewGetLayers( layers, layers_count );
    aItem->viewPrivData()->saveLayers( layers, layers_count );

    m_allItems->push_back( aItem );

    for( int i = 0; i < layers_count; ++i )
    {
        VIEW_LAYER& l = m_layers[layers[i]];
        l.items->Insert( aItem );
        MarkTargetDirty( l.target );
    }

    SetVisible( aItem, true );
    Update( aItem, KIGFX::INITIAL_ADD );
}


void VIEW::Remove( VIEW_ITEM* aItem )
{
    if( !aItem )
        return;

    auto viewData = aItem->viewPrivData();

    if( !viewData )
        return;

    wxCHECK( viewData->m_view == this, /*void*/ );
    auto item = std::find( m_allItems->begin(), m_allItems->end(), aItem );

    if( item != m_allItems->end() )
    {
        m_allItems->erase( item );
        viewData->clearUpdateFlags();
    }

    int layers[VIEW::VIEW_MAX_LAYERS], layers_count;
    viewData->getLayers( layers, layers_count );

    for( int i = 0; i < layers_count; ++i )
    {
        VIEW_LAYER& l = m_layers[layers[i]];
        l.items->Remove( aItem );
        MarkTargetDirty( l.target );

        // Clear the GAL cache
        int prevGroup = viewData->getGroup( layers[i] );

        if( prevGroup >= 0 )
            m_gal->DeleteGroup( prevGroup );
    }

    viewData->deleteGroups();
    viewData->m_view = nullptr;
}


void VIEW::SetRequired( int aLayerId, int aRequiredId, bool aRequired )
{
    wxCHECK( (unsigned) aLayerId < m_layers.size(), /*void*/ );
    wxCHECK( (unsigned) aRequiredId < m_layers.size(), /*void*/ );

    if( aRequired )
        m_layers[aLayerId].requiredLayers.insert( aRequiredId );
    else
        m_layers[aLayerId].requiredLayers.erase( aRequired );
}


// stupid C++... python lambda would do this in one line
template <class CONTAINER>
struct QUERY_VISITOR
{
    typedef typename CONTAINER::value_type item_type;

    QUERY_VISITOR( CONTAINER& aCont, int aLayer ) :
        m_cont( aCont ), m_layer( aLayer )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        if( aItem->viewPrivData()->GetFlags() & VISIBLE )
            m_cont.push_back( VIEW::LAYER_ITEM_PAIR( aItem, m_layer ) );

        return true;
    }

    CONTAINER&  m_cont;
    int         m_layer;
};


int VIEW::Query( const BOX2I& aRect, std::vector<LAYER_ITEM_PAIR>& aResult ) const
{
    if( m_orderedLayers.empty() )
        return 0;

    std::vector<VIEW_LAYER*>::const_reverse_iterator i;

    // execute queries in reverse direction, so that items that are on the top of
    // the rendering stack are returned first.
    for( i = m_orderedLayers.rbegin(); i != m_orderedLayers.rend(); ++i )
    {
        // ignore layers that do not contain actual items (i.e. the selection box, menus, floats)
        if( ( *i )->displayOnly || !( *i )->visible )
            continue;

        QUERY_VISITOR<std::vector<LAYER_ITEM_PAIR> > visitor( aResult, ( *i )->id );
        ( *i )->items->Query( aRect, visitor );
    }

    return aResult.size();
}


VECTOR2D VIEW::ToWorld( const VECTOR2D& aCoord, bool aAbsolute ) const
{
    const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix();

    if( aAbsolute )
        return VECTOR2D( matrix * aCoord );
    else
        return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y );
}


double VIEW::ToWorld( double aSize ) const
{
    const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix();

    return fabs( matrix.GetScale().x * aSize );
}


VECTOR2D VIEW::ToScreen( const VECTOR2D& aCoord, bool aAbsolute ) const
{
    const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix();

    if( aAbsolute )
        return VECTOR2D( matrix * aCoord );
    else
        return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y );
}


double VIEW::ToScreen( double aSize ) const
{
    const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix();

    return matrix.GetScale().x * aSize;
}


void VIEW::CopySettings( const VIEW* aOtherView )
{
    wxASSERT_MSG( false, wxT( "This is not implemented" ) );
}


void VIEW::SetGAL( GAL* aGal )
{
    bool recacheGroups = ( m_gal != nullptr );    // recache groups only if GAL is reassigned
    m_gal = aGal;

    // clear group numbers, so everything is going to be recached
    if( recacheGroups )
        clearGroupCache();

    // every target has to be refreshed
    MarkDirty();

    // force the new GAL to display the current viewport.
    SetCenter( m_center );
    SetScale( m_scale );
    SetMirror( m_mirrorX, m_mirrorY );
}


BOX2D VIEW::GetViewport() const
{
    BOX2D    rect;
    VECTOR2D screenSize = m_gal->GetScreenPixelSize();

    rect.SetOrigin( ToWorld( VECTOR2D( 0, 0 ) ) );
    rect.SetEnd( ToWorld( screenSize ) );

    return rect.Normalize();
}


void VIEW::SetViewport( const BOX2D& aViewport )
{
    VECTOR2D ssize = ToWorld( m_gal->GetScreenPixelSize(), false );

    wxCHECK( ssize.x > 0 && ssize.y > 0, /*void*/ );

    VECTOR2D centre = aViewport.Centre();
    VECTOR2D vsize  = aViewport.GetSize();
    double   zoom   = 1.0 / std::max( fabs( vsize.x / ssize.x ), fabs( vsize.y / ssize.y ) );

    SetCenter( centre );
    SetScale( GetScale() * zoom );
}


void VIEW::SetMirror( bool aMirrorX, bool aMirrorY )
{
    wxASSERT_MSG( !aMirrorY, _( "Mirroring for Y axis is not supported yet" ) );

    m_mirrorX = aMirrorX;
    m_mirrorY = aMirrorY;
    m_gal->SetFlip( aMirrorX, aMirrorY );

    // Redraw everything
    MarkDirty();
}


void VIEW::SetScale( double aScale, VECTOR2D aAnchor )
{
    if( aAnchor == VECTOR2D( 0, 0 ) )
        aAnchor = m_center;

    VECTOR2D a = ToScreen( aAnchor );

    if( aScale < m_minScale )
        m_scale = m_minScale;
    else if( aScale > m_maxScale )
        m_scale = m_maxScale;
    else
        m_scale = aScale;

    m_gal->SetZoomFactor( m_scale );
    m_gal->ComputeWorldScreenMatrix();

    VECTOR2D delta = ToWorld( a ) - aAnchor;

    SetCenter( m_center - delta );

    // Redraw everything after the viewport has changed
    MarkDirty();
}


void VIEW::SetCenter( const VECTOR2D& aCenter )
{
    m_center = aCenter;

    if( !m_boundary.Contains( aCenter ) )
    {
        if( m_center.x < m_boundary.GetLeft() )
            m_center.x = m_boundary.GetLeft();
        else if( aCenter.x > m_boundary.GetRight() )
            m_center.x = m_boundary.GetRight();

        if( m_center.y < m_boundary.GetTop() )
            m_center.y = m_boundary.GetTop();
        else if( m_center.y > m_boundary.GetBottom() )
            m_center.y = m_boundary.GetBottom();
    }

    m_gal->SetLookAtPoint( m_center );
    m_gal->ComputeWorldScreenMatrix();

    // Redraw everything after the viewport has changed
    MarkDirty();
}


void VIEW::SetCenter( const VECTOR2D& aCenter, const std::vector<BOX2D>& obscuringScreenRects )
{
    if( obscuringScreenRects.empty() )
        return SetCenter( aCenter );

    BOX2D          screenRect( { 0, 0 }, m_gal->GetScreenPixelSize() );
    SHAPE_POLY_SET unobscuredPoly( screenRect );
    VECTOR2D       unobscuredCenter = screenRect.Centre();

    for( const BOX2D& obscuringScreenRect : obscuringScreenRects )
    {
        SHAPE_POLY_SET obscuringPoly( obscuringScreenRect );
        unobscuredPoly.BooleanSubtract( obscuringPoly, SHAPE_POLY_SET::PM_FAST );
    }

    /*
     * Perform a step-wise deflate to find the center of the largest unobscured area
     */

    BOX2I bbox = unobscuredPoly.BBox();
    int   step = std::min( bbox.GetWidth(), bbox.GetHeight() ) / 10;

    while( !unobscuredPoly.IsEmpty() )
    {
        unobscuredCenter = (wxPoint) unobscuredPoly.BBox().Centre();
        unobscuredPoly.Deflate( step, 4 );
    }

    SetCenter( aCenter - ToWorld( unobscuredCenter - screenRect.Centre(), false ) );
}


void VIEW::SetLayerOrder( int aLayer, int aRenderingOrder )
{
    m_layers[aLayer].renderingOrder = aRenderingOrder;

    sortLayers();
}


int VIEW::GetLayerOrder( int aLayer ) const
{
    return m_layers.at( aLayer ).renderingOrder;
}


void VIEW::SortLayers( int aLayers[], int& aCount ) const
{
    int maxLay, maxOrd, maxIdx;

    for( int i = 0; i < aCount; ++i )
    {
        maxLay = aLayers[i];
        maxOrd = GetLayerOrder( maxLay );
        maxIdx = i;

        // Look for the max element in the range (j..aCount)
        for( int j = i; j < aCount; ++j )
        {
            if( maxOrd < GetLayerOrder( aLayers[j] ) )
            {
                maxLay = aLayers[j];
                maxOrd = GetLayerOrder( maxLay );
                maxIdx = j;
            }
        }

        // Swap elements
        aLayers[maxIdx] = aLayers[i];
        aLayers[i] = maxLay;
    }
}


void VIEW::ReorderLayerData( std::unordered_map<int, int> aReorderMap )
{
    std::vector<VIEW_LAYER> new_map;
    new_map.reserve( m_layers.size() );

    for( const VIEW_LAYER& layer : m_layers )
        new_map.push_back( layer );

    for( auto& pair : aReorderMap )
    {
        new_map[pair.second] = m_layers[pair.first];
        new_map[pair.second].id = pair.second;
    }

    m_layers = new_map;

    for( VIEW_ITEM* item : *m_allItems )
    {
        VIEW_ITEM_DATA* viewData = item->viewPrivData();

        if( !viewData )
            continue;

        int layers[VIEW::VIEW_MAX_LAYERS], layers_count;

        item->ViewGetLayers( layers, layers_count );
        viewData->saveLayers( layers, layers_count );

        viewData->reorderGroups( aReorderMap );

        viewData->m_requiredUpdate |= COLOR;
    }

    UpdateItems();
}


struct VIEW::UPDATE_COLOR_VISITOR
{
    UPDATE_COLOR_VISITOR( int aLayer, PAINTER* aPainter, GAL* aGal ) :
        layer( aLayer ),
        painter( aPainter ),
        gal( aGal )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        // Obtain the color that should be used for coloring the item
        const COLOR4D color = painter->GetSettings()->GetColor( aItem, layer );
        int           group = aItem->viewPrivData()->getGroup( layer );

        if( group >= 0 )
            gal->ChangeGroupColor( group, color );

        return true;
    }

    int layer;
    PAINTER* painter;
    GAL* gal;
};


void VIEW::UpdateLayerColor( int aLayer )
{
    // There is no point in updating non-cached layers
    if( !IsCached( aLayer ) )
        return;

    BOX2I r;

    r.SetMaximum();

    if( m_gal->IsVisible() )
    {
        GAL_UPDATE_CONTEXT ctx( m_gal );

        UPDATE_COLOR_VISITOR visitor( aLayer, m_painter, m_gal );
        m_layers[aLayer].items->Query( r, visitor );
        MarkTargetDirty( m_layers[aLayer].target );
    }
}


void VIEW::UpdateAllLayersColor()
{
    if( m_gal->IsVisible() )
    {
        GAL_UPDATE_CONTEXT ctx( m_gal );

        for( VIEW_ITEM* item : *m_allItems )
        {
            VIEW_ITEM_DATA* viewData = item->viewPrivData();

            if( !viewData )
                continue;

            int layers[VIEW::VIEW_MAX_LAYERS], layers_count;
            viewData->getLayers( layers, layers_count );

            for( int i = 0; i < layers_count; ++i )
            {
                const COLOR4D color = m_painter->GetSettings()->GetColor( item, layers[i] );
                int           group = viewData->getGroup( layers[i] );

                if( group >= 0 )
                    m_gal->ChangeGroupColor( group, color );
            }
        }
    }

    MarkDirty();
}


struct VIEW::UPDATE_DEPTH_VISITOR
{
    UPDATE_DEPTH_VISITOR( int aLayer, int aDepth, GAL* aGal ) :
        layer( aLayer ),
        depth( aDepth ),
        gal( aGal )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        int group = aItem->viewPrivData()->getGroup( layer );

        if( group >= 0 )
            gal->ChangeGroupDepth( group, depth );

        return true;
    }

    int layer, depth;
    GAL* gal;
};


int VIEW::GetTopLayer() const
{
    if( m_topLayers.size() == 0 )
        return 0;

    return *m_topLayers.begin();
}


void VIEW::SetTopLayer( int aLayer, bool aEnabled )
{
    if( aEnabled )
    {
        if( m_topLayers.count( aLayer ) == 1 )
            return;

        m_topLayers.insert( aLayer );

        // Move the layer closer to front
        if( m_enableOrderModifier )
            m_layers[aLayer].renderingOrder += TOP_LAYER_MODIFIER;
    }
    else
    {
        if( m_topLayers.count( aLayer ) == 0 )
            return;

        m_topLayers.erase( aLayer );

        // Restore the previous rendering order
        if( m_enableOrderModifier )
            m_layers[aLayer].renderingOrder -= TOP_LAYER_MODIFIER;
    }
}


void VIEW::EnableTopLayer( bool aEnable )
{
    if( aEnable == m_enableOrderModifier )
        return;

    m_enableOrderModifier = aEnable;

    std::set<unsigned int>::iterator it;

    if( aEnable )
    {
        for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it )
            m_layers[*it].renderingOrder += TOP_LAYER_MODIFIER;
    }
    else
    {
        for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it )
            m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER;
    }

    UpdateAllLayersOrder();
    UpdateAllLayersColor();
}


void VIEW::ClearTopLayers()
{
    std::set<unsigned int>::iterator it;

    if( m_enableOrderModifier )
    {
        // Restore the previous rendering order for layers that were marked as top
        for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it )
            m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER;
    }

    m_topLayers.clear();
}


void VIEW::UpdateAllLayersOrder()
{
    sortLayers();

    if( m_gal->IsVisible() )
    {
        GAL_UPDATE_CONTEXT ctx( m_gal );

        for( VIEW_ITEM* item : *m_allItems )
        {
            VIEW_ITEM_DATA* viewData = item->viewPrivData();

            if( !viewData )
                continue;

            int layers[VIEW::VIEW_MAX_LAYERS], layers_count;
            viewData->getLayers( layers, layers_count );

            for( int i = 0; i < layers_count; ++i )
            {
                int group = viewData->getGroup( layers[i] );

                if( group >= 0 )
                    m_gal->ChangeGroupDepth( group, m_layers[layers[i]].renderingOrder );
            }
        }
    }

    MarkDirty();
}


struct VIEW::DRAW_ITEM_VISITOR
{
    DRAW_ITEM_VISITOR( VIEW* aView, int aLayer, bool aUseDrawPriority, bool aReverseDrawOrder ) :
        view( aView ),
        layer( aLayer ),
        useDrawPriority( aUseDrawPriority ),
        reverseDrawOrder( aReverseDrawOrder )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        wxCHECK( aItem->viewPrivData(), false );

        // Conditions that have to be fulfilled for an item to be drawn
        bool drawCondition = aItem->viewPrivData()->isRenderable()
                                    && aItem->ViewGetLOD( layer, view ) < view->m_scale;
        if( !drawCondition )
            return true;

        if( useDrawPriority )
            drawItems.push_back( aItem );
        else
            view->draw( aItem, layer );

        return true;
    }

    void deferredDraw()
    {
        if( reverseDrawOrder )
        {
            std::sort( drawItems.begin(), drawItems.end(),
                       []( VIEW_ITEM* a, VIEW_ITEM* b ) -> bool
                       {
                           return b->viewPrivData()->m_drawPriority < a->viewPrivData()->m_drawPriority;
                       });
        }
        else
        {
            std::sort( drawItems.begin(), drawItems.end(),
                       []( VIEW_ITEM* a, VIEW_ITEM* b ) -> bool
                       {
                           return a->viewPrivData()->m_drawPriority < b->viewPrivData()->m_drawPriority;
                       });
        }

        for( VIEW_ITEM* item : drawItems )
            view->draw( item, layer );
    }

    VIEW* view;
    int layer, layers[VIEW_MAX_LAYERS];
    bool useDrawPriority, reverseDrawOrder;
    std::vector<VIEW_ITEM*> drawItems;
};


void VIEW::redrawRect( const BOX2I& aRect )
{
    for( VIEW_LAYER* l : m_orderedLayers )
    {
        if( l->visible && IsTargetDirty( l->target ) && areRequiredLayersEnabled( l->id ) )
        {
            DRAW_ITEM_VISITOR drawFunc( this, l->id, m_useDrawPriority, m_reverseDrawOrder );

            m_gal->SetTarget( l->target );
            m_gal->SetLayerDepth( l->renderingOrder );

            // Differential layer also work for the negatives, since both special layer types
            // will composite on separate layers (at least in Cairo)
            if( l->diffLayer )
                m_gal->StartDiffLayer();
            else if( l->hasNegatives )
                m_gal->StartNegativesLayer();


            l->items->Query( aRect, drawFunc );

            if( m_useDrawPriority )
                drawFunc.deferredDraw();

            if( l->diffLayer )
                m_gal->EndDiffLayer();
            else if( l->hasNegatives )
                m_gal->EndNegativesLayer();
        }
    }
}


void VIEW::draw( VIEW_ITEM* aItem, int aLayer, bool aImmediate )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

    if( !viewData )
        return;

    if( IsCached( aLayer ) && !aImmediate )
    {
        // Draw using cached information or create one
        int group = viewData->getGroup( aLayer );

        if( group >= 0 )
            m_gal->DrawGroup( group );
        else
            Update( aItem );
    }
    else
    {
        // Immediate mode
        if( !m_painter->Draw( aItem, aLayer ) )
            aItem->ViewDraw( aLayer, this );  // Alternative drawing method
    }
}


void VIEW::draw( VIEW_ITEM* aItem, bool aImmediate )
{
    int layers[VIEW_MAX_LAYERS], layers_count;

    aItem->ViewGetLayers( layers, layers_count );

    // Sorting is needed for drawing order dependent GALs (like Cairo)
    SortLayers( layers, layers_count );

    for( int i = 0; i < layers_count; ++i )
    {
        m_gal->SetLayerDepth( m_layers.at( layers[i] ).renderingOrder );
        draw( aItem, layers[i], aImmediate );
    }
}


void VIEW::draw( VIEW_GROUP* aGroup, bool aImmediate )
{
    for( unsigned int i = 0; i < aGroup->GetSize(); i++)
        draw( aGroup->GetItem(i), aImmediate );
}


struct VIEW::RECACHE_ITEM_VISITOR
{
    RECACHE_ITEM_VISITOR( VIEW* aView, GAL* aGal, int aLayer ) :
        view( aView ),
        gal( aGal ),
        layer( aLayer )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

        if( !viewData )
            return false;

        // Remove previously cached group
        int group = viewData->getGroup( layer );

        if( group >= 0 )
            gal->DeleteGroup( group );

        viewData->setGroup( layer, -1 );
        view->Update( aItem );

        return true;
    }

    VIEW* view;
    GAL* gal;
    int layer;
};


void VIEW::Clear()
{
    BOX2I r;
    r.SetMaximum();
    m_allItems->clear();

    for( VIEW_LAYER& layer : m_layers )
        layer.items->RemoveAll();

    m_nextDrawPriority = 0;

    m_gal->ClearCache();
}


void VIEW::ClearTargets()
{
    if( IsTargetDirty( TARGET_CACHED ) || IsTargetDirty( TARGET_NONCACHED ) )
    {
        // TARGET_CACHED and TARGET_NONCACHED have to be redrawn together, as they contain
        // layers that rely on each other (eg. netnames are noncached, but tracks - are cached)
        m_gal->ClearTarget( TARGET_NONCACHED );
        m_gal->ClearTarget( TARGET_CACHED );

        MarkDirty();
    }

    if( IsTargetDirty( TARGET_OVERLAY ) )
    {
        m_gal->ClearTarget( TARGET_OVERLAY );
    }
}


void VIEW::Redraw()
{
#ifdef KICAD_GAL_PROFILE
    PROF_COUNTER totalRealTime;
#endif /* KICAD_GAL_PROFILE */

    VECTOR2D screenSize = m_gal->GetScreenPixelSize();
    BOX2D    rect( ToWorld( VECTOR2D( 0, 0 ) ),
                   ToWorld( screenSize ) - ToWorld( VECTOR2D( 0, 0 ) ) );

    rect.Normalize();
    BOX2I recti( rect.GetPosition(), rect.GetSize() );

    // The view rtree uses integer positions.  Large screens can overflow this size so in
    // this case, simply set the rectangle to the full rtree.
    if( rect.GetWidth() > std::numeric_limits<int>::max()
            || rect.GetHeight() > std::numeric_limits<int>::max() )
    {
        recti.SetMaximum();
    }

    redrawRect( recti );

    // All targets were redrawn, so nothing is dirty
    MarkClean();

#ifdef KICAD_GAL_PROFILE
    totalRealTime.Stop();
    wxLogTrace( traceGalProfile, wxT( "VIEW::Redraw(): %.1f ms" ), totalRealTime.msecs() );
#endif /* KICAD_GAL_PROFILE */
}


const VECTOR2I& VIEW::GetScreenPixelSize() const
{
    return m_gal->GetScreenPixelSize();
}


struct VIEW::CLEAR_LAYER_CACHE_VISITOR
{
    CLEAR_LAYER_CACHE_VISITOR( VIEW* aView ) :
        view( aView )
    {
    }

    bool operator()( VIEW_ITEM* aItem )
    {
        aItem->viewPrivData()->deleteGroups();

        return true;
    }

    VIEW* view;
};


void VIEW::clearGroupCache()
{
    BOX2I r;

    r.SetMaximum();
    CLEAR_LAYER_CACHE_VISITOR visitor( this );

    for( VIEW_LAYER& layer : m_layers )
        layer.items->Query( r, visitor );
}


void VIEW::invalidateItem( VIEW_ITEM* aItem, int aUpdateFlags )
{
    if( aUpdateFlags & INITIAL_ADD )
    {
        // Don't update layers or bbox, since it was done in VIEW::Add()
        // Now that we have initialized, set flags to ALL for the code below
        aUpdateFlags = ALL;
    }
    else
    {
        // updateLayers updates geometry too, so we do not have to update both of them at the
        // same time
        if( aUpdateFlags & LAYERS )
            updateLayers( aItem );
        else if( aUpdateFlags & GEOMETRY )
            updateBbox( aItem );
    }

    int layers[VIEW_MAX_LAYERS], layers_count;
    aItem->ViewGetLayers( layers, layers_count );

    // Iterate through layers used by the item and recache it immediately
    for( int i = 0; i < layers_count; ++i )
    {
        int layerId = layers[i];

        if( IsCached( layerId ) )
        {
            if( aUpdateFlags & ( GEOMETRY | LAYERS | REPAINT ) )
                updateItemGeometry( aItem, layerId );
            else if( aUpdateFlags & COLOR )
                updateItemColor( aItem, layerId );
        }

        // Mark those layers as dirty, so the VIEW will be refreshed
        MarkTargetDirty( m_layers[layerId].target );
    }

    aItem->viewPrivData()->clearUpdateFlags();
}


void VIEW::sortLayers()
{
    int n = 0;

    m_orderedLayers.resize( m_layers.size() );

    for( VIEW_LAYER& layer : m_layers )
        m_orderedLayers[n++] = &layer;

    sort( m_orderedLayers.begin(), m_orderedLayers.end(), compareRenderingOrder );

    MarkDirty();
}


void VIEW::updateItemColor( VIEW_ITEM* aItem, int aLayer )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();
    wxCHECK( (unsigned) aLayer < m_layers.size(), /*void*/ );
    wxCHECK( IsCached( aLayer ), /*void*/ );

    if( !viewData )
        return;

    // Obtain the color that should be used for coloring the item on the specific layerId
    const COLOR4D color = m_painter->GetSettings()->GetColor( aItem, aLayer );
    int group = viewData->getGroup( aLayer );

    // Change the color, only if it has group assigned
    if( group >= 0 )
        m_gal->ChangeGroupColor( group, color );
}


void VIEW::updateItemGeometry( VIEW_ITEM* aItem, int aLayer )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();
    wxCHECK( (unsigned) aLayer < m_layers.size(), /*void*/ );
    wxCHECK( IsCached( aLayer ), /*void*/ );

    if( !viewData )
        return;

    VIEW_LAYER& l = m_layers.at( aLayer );

    m_gal->SetTarget( l.target );
    m_gal->SetLayerDepth( l.renderingOrder );

    // Redraw the item from scratch
    int group = viewData->getGroup( aLayer );

    if( group >= 0 )
        m_gal->DeleteGroup( group );

    group = m_gal->BeginGroup();
    viewData->setGroup( aLayer, group );

    if( !m_painter->Draw( static_cast<EDA_ITEM*>( aItem ), aLayer ) )
        aItem->ViewDraw( aLayer, this ); // Alternative drawing method

    m_gal->EndGroup();
}


void VIEW::updateBbox( VIEW_ITEM* aItem )
{
    int layers[VIEW_MAX_LAYERS], layers_count;

    aItem->ViewGetLayers( layers, layers_count );

    for( int i = 0; i < layers_count; ++i )
    {
        VIEW_LAYER& l = m_layers[layers[i]];
        l.items->Remove( aItem );
        l.items->Insert( aItem );
        MarkTargetDirty( l.target );
    }
}


void VIEW::updateLayers( VIEW_ITEM* aItem )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();
    int             layers[VIEW_MAX_LAYERS], layers_count;

    if( !viewData )
        return;

    // Remove the item from previous layer set
    viewData->getLayers( layers, layers_count );

    for( int i = 0; i < layers_count; ++i )
    {
        VIEW_LAYER& l = m_layers[layers[i]];
        l.items->Remove( aItem );
        MarkTargetDirty( l.target );

        if( IsCached( l.id ) )
        {
            // Redraw the item from scratch
            int prevGroup = viewData->getGroup( layers[i] );

            if( prevGroup >= 0 )
            {
                m_gal->DeleteGroup( prevGroup );
                viewData->setGroup( l.id, -1 );
            }
        }
    }

    // Add the item to new layer set
    aItem->ViewGetLayers( layers, layers_count );
    viewData->saveLayers( layers, layers_count );

    for( int i = 0; i < layers_count; i++ )
    {
        VIEW_LAYER& l = m_layers[layers[i]];
        l.items->Insert( aItem );
        MarkTargetDirty( l.target );
    }
}


bool VIEW::areRequiredLayersEnabled( int aLayerId ) const
{
    wxCHECK( (unsigned) aLayerId < m_layers.size(), false );

    std::set<int>::const_iterator it, it_end;

    for( int layer : m_layers.at( aLayerId ).requiredLayers )
    {
        // That is enough if just one layer is not enabled
        if( !m_layers.at( layer ).visible || !areRequiredLayersEnabled( layer ) )
            return false;
    }

    return true;
}


void VIEW::RecacheAllItems()
{
    BOX2I r;

    r.SetMaximum();

    for( const VIEW_LAYER& l : m_layers )
    {
        if( IsCached( l.id ) )
        {
            RECACHE_ITEM_VISITOR visitor( this, m_gal, l.id );
            l.items->Query( r, visitor );
        }
    }
}


void VIEW::UpdateItems()
{
    if( !m_gal->IsVisible() )
        return;

    unsigned int cntGeomUpdate = 0;
    unsigned int cntAnyUpdate = 0;

    for( VIEW_ITEM* item : *m_allItems )
    {
        auto vpd = item->viewPrivData();

        if( !vpd )
            continue;

        if( vpd->m_requiredUpdate & ( GEOMETRY | LAYERS ) )
        {
            cntGeomUpdate++;
        }
        if( vpd->m_requiredUpdate != NONE )
        {
            cntAnyUpdate++;
        }
    }

    unsigned int cntTotal = m_allItems->size();

    double ratio = (double) cntGeomUpdate / (double) cntTotal;

    // Optimization to improve view update time. If a lot of items (say, 30%) have their
    // bboxes/geometry changed it's way faster (around 10 times) to rebuild the R-Trees
    // from scratch rather than update the bbox of each changed item. Pcbnew does multiple
    // full geometry updates during file load, this can save a solid 30 seconds on load time
    // for larger designs...

    if( ratio > 0.3 )
    {
        auto allItems = *m_allItems;
        int  layers[VIEW_MAX_LAYERS], layers_count;

        // kill all Rtrees
        for( VIEW_LAYER& layer : m_layers )
            layer.items->RemoveAll();

        // and re-insert items from scratch
        for( VIEW_ITEM* item : allItems )
        {
            item->ViewGetLayers( layers, layers_count );
            item->viewPrivData()->saveLayers( layers, layers_count );

            for( int i = 0; i < layers_count; ++i )
            {
                VIEW_LAYER& l = m_layers[layers[i]];
                l.items->Insert( item );
                MarkTargetDirty( l.target );
            }

            item->viewPrivData()->m_requiredUpdate &= ~( LAYERS | GEOMETRY );
        }
    }

    if( cntAnyUpdate )
    {
        GAL_UPDATE_CONTEXT ctx( m_gal );

        for( VIEW_ITEM* item : *m_allItems.get() )
        {
            if( item->viewPrivData() && item->viewPrivData()->m_requiredUpdate != NONE )
            {
                invalidateItem( item, item->viewPrivData()->m_requiredUpdate );
                item->viewPrivData()->m_requiredUpdate = NONE;
            }
        }
    }

    KI_TRACE( traceGalProfile, wxT( "View update: total items %u, geom %u updates %u\n" ), cntTotal,
              cntGeomUpdate, cntAnyUpdate );
}


void VIEW::UpdateAllItems( int aUpdateFlags )
{
    for( VIEW_ITEM* item : *m_allItems )
    {
        if( item->viewPrivData() )
            item->viewPrivData()->m_requiredUpdate |= aUpdateFlags;
    }
}


void VIEW::UpdateAllItemsConditionally( int aUpdateFlags,
                                        std::function<bool( VIEW_ITEM* )> aCondition )
{
    for( VIEW_ITEM* item : *m_allItems )
    {
        if( aCondition( item ) )
        {
            if( item->viewPrivData() )
                item->viewPrivData()->m_requiredUpdate |= aUpdateFlags;
        }
    }
}


std::unique_ptr<VIEW> VIEW::DataReference() const
{
    std::unique_ptr<VIEW> ret = std::make_unique<VIEW>();
    ret->m_allItems = m_allItems;
    ret->m_layers = m_layers;
    ret->sortLayers();
    return ret;
}


void VIEW::SetVisible( VIEW_ITEM* aItem, bool aIsVisible )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

    if( !viewData )
        return;

    bool cur_visible = viewData->m_flags & VISIBLE;

    if( cur_visible != aIsVisible )
    {
        if( aIsVisible )
            viewData->m_flags |= VISIBLE;
        else
            viewData->m_flags &= ~VISIBLE;

        Update( aItem, APPEARANCE | COLOR );
    }
}


void VIEW::Hide( VIEW_ITEM* aItem, bool aHide )
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

    if( !viewData )
        return;

    if( !( viewData->m_flags & VISIBLE ) )
        return;

    if( aHide )
        viewData->m_flags |= HIDDEN;
    else
        viewData->m_flags &= ~HIDDEN;

    Update( aItem, APPEARANCE );
}


bool VIEW::IsVisible( const VIEW_ITEM* aItem ) const
{
    const VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

    return viewData && ( viewData->m_flags & VISIBLE );
}


void VIEW::Update( const VIEW_ITEM* aItem ) const
{
    Update( aItem, ALL );
}


void VIEW::Update( const VIEW_ITEM* aItem, int aUpdateFlags ) const
{
    VIEW_ITEM_DATA* viewData = aItem->viewPrivData();

    if( !viewData )
        return;

    assert( aUpdateFlags != NONE );

    viewData->m_requiredUpdate |= aUpdateFlags;
}


std::shared_ptr<VIEW_OVERLAY> VIEW::MakeOverlay()
{
    std::shared_ptr<VIEW_OVERLAY> overlay( new VIEW_OVERLAY );

    Add( overlay.get() );
    return overlay;
}


void VIEW::ClearPreview()
{
    if( !m_preview )
        return;

    m_preview->Clear();

    for( EDA_ITEM *item : m_ownedItems )
        delete item;

    m_ownedItems.clear();
    Update( m_preview.get() );
}


void VIEW::InitPreview()
{
   m_preview.reset( new KIGFX::VIEW_GROUP() );
   Add( m_preview.get() );
}


void VIEW::AddToPreview( EDA_ITEM* aItem, bool aTakeOwnership )
{
   Hide( aItem, false );
   m_preview->Add( aItem );

   if( aTakeOwnership )
       m_ownedItems.push_back( aItem );

   SetVisible( m_preview.get(), true );
   Hide( m_preview.get(), false );
   Update( m_preview.get() );
}


void VIEW::ShowPreview( bool aShow )
{
   SetVisible( m_preview.get(), aShow );
}


const int VIEW::TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS;

} // namespace KIGFX