/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright The 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifndef LSET_H
#define LSET_H

#include <layer_ids.h>
#include <base_set.h>

class LSEQ;
class LAYER_RANGE;

/**
 * LSET is a set of PCB_LAYER_IDs.
 *
 * It can be converted to numerous purpose LSEQs using the various member functions, most of
 * which are based on Seq(). The advantage of converting to #LSEQ using purposeful code, is it
 * removes any dependency on order/sequence inherent in this set.
 */
class KICOMMON_API LSET : public BASE_SET
{
public:

    /**
     * Create an empty (cleared) set.
     */
    LSET() : BASE_SET( PCB_LAYER_ID_COUNT ) {} // all bits are set to zero in BASE_SET()

    LSET( const BASE_SET& aOther ) : BASE_SET( aOther ) {}

    LSET( std::initializer_list<PCB_LAYER_ID> aList );

    LSET( const std::vector<PCB_LAYER_ID>& aList );

    LSET( const LSEQ& aSeq );

    LSET( const LAYER_RANGE& aRange );

    LSET( unsigned long __val ) = delete;

    /**
     * See if the layer set contains a PCB layer.
     *
     * @param aLayer is the layer to check.
     * @return true if the layer is included.
     */
    bool Contains( PCB_LAYER_ID aLayer ) const
    {
        // At the moment, LSET cannot store negative layers, but PCB_LAYER_ID can contain them
        if( aLayer < 0 )
           return false;

        try
        {
            return test( aLayer );
        }
        catch( std::out_of_range& )
        {
            return false;
        }
    }

    /**
     * See if this layer set contains all layers in another set.
     *
     * @param aLayers is the set to check.
     * @return true if every layer in @a aLayers is included in this set.
     */
    bool ContainsAll( const LSET& aLayers ) const
    {
        return aLayers.is_subset_of( *this );
    }

    /**
     * Return the fixed name association with @a aLayerId.
     *
     * @note These names must not be translated or changed.  They are used as tokens in the board
     * file format because the ordinal value of the PCB_LAYER_ID enum was not stable over time.
     * @see LayerName() for what should be used to display the default name of a layer in the GUI.
     */
    static wxString Name( PCB_LAYER_ID aLayerId );

    /**
     * Return the layer number from a layer name.
     */
    static int NameToLayer( wxString& aName );

    /**
     * Return true if aLayer is between aStart and aEnd, inclusive.
     *
     * This takes into account the direction of the layers and the fact that #B_Cu comes before
     * In*_Cu.
     */
    static bool IsBetween( PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd, PCB_LAYER_ID aLayer );

    /**
     * Return a complete set of internal copper layers which is all Cu layers
     * except #F_Cu and #B_Cu.
     */
    static const LSET& InternalCuMask();

    /**
     * Return a complete set of all top assembly layers which is all #F_SilkS and #F_Mask.
     */
    static const LSET& FrontAssembly();

    /**
     * Return a complete set of all bottom assembly layers which is all #B_SilkS and #B_Mask.
     */
    static const LSET& BackAssembly();

    /**
     * Return a mask holding the requested number of Cu PCB_LAYER_IDs.
     */
    static LSET AllCuMask( int aCuLayerCount );

    /**
     * return AllCuMask( MAX_CU_LAYERS );
     */
    static const LSET& AllCuMask();

    /**
     * Return a mask holding the Front and Bottom layers.
     */
    static const LSET& ExternalCuMask();

    /**
     * Return a mask holding all layer minus CU layers.
     */
    static LSET AllNonCuMask();

    static const LSET& AllLayersMask();

    /**
     * Return a mask holding all technical layers (no CU layer) on front side.
     */
    static const LSET& FrontTechMask();

    /**
     * Return a mask holding technical layers used in a board fabrication
     * (no CU layer) on front side.
     */
    static const LSET& FrontBoardTechMask();

    /**
     * Return a mask holding all technical layers (no CU layer) on back side.
     */
    static const LSET& BackTechMask();

    /**
     * Return a mask holding technical layers used in a board fabrication
     * (no CU layer) on Back side.
     */
    static const LSET& BackBoardTechMask();

    /**
     * Return a mask holding all technical layers (no CU layer) on both side.
     */
    static const LSET& AllTechMask();

    /**
     * Return a mask holding board technical layers (no CU layer) on both side.
     */
    static const LSET& AllBoardTechMask();

    /**
     * Return a mask holding all technical layers and the external CU layer on front side.
     */
    static const LSET& FrontMask();

    /**
     * Return a mask holding all technical layers and the external CU layer on back side.
     */
    static const LSET& BackMask();

    static const LSET& SideSpecificMask();

    static const LSET& UserMask();

    /**
     * Return a mask holding all layers which are physically realized.
     *
     * Equivalent to the copper layers + the board tech mask.
     */
    static const LSET& PhysicalLayersMask();

    /**
     * Return a mask with the requested number of user defined layers.
     *
     * @param aUserDefinedLayerCount The number of user defined layers
     */
    static LSET UserDefinedLayersMask( int aUserDefinedLayerCount = MAX_USER_DEFINED_LAYERS );

    /**
     * Return a sequence of copper layers in starting from the front/top
     * and extending to the back/bottom.
     */
    LSEQ CuStack() const;

    /**
     * Return the technical and user layers in the order shown in layer widget.
     */
    LSEQ TechAndUserUIOrder() const;

    /**
     * Return the copper, technical and user layers in the order shown in layer widget.
     */
    LSEQ UIOrder() const;

    /**
     * Return an #LSEQ from the union of this #LSET and a desired sequence.
     *
     * The #LSEQ element will be in the same sequence as aWishListSequence if they are present.
     *
     * @param aWishListSequence establishes the order of the returned LSEQ, and the LSEQ will only
     *        contain PCB_LAYER_IDs which are present in this set.
     */
    LSEQ Seq( const LSEQ& aSequence ) const;

    /**
     * Return a #LSEQ from this #LSET in ascending PCB_LAYER_ID order.
     *
     * Each #LSEQ element will be in the same sequence as in PCB_LAYER_ID and only present
     * in the resultant #LSEQ if present in this set.  Therefore the sequence is subject to
     * change, use it only when enumeration and not order is important.
     */
    LSEQ Seq() const;

    /**
     * Generate a sequence of layers that represent a top to bottom stack of this set of layers.
     *
     * @param aSelectedLayer is the layer to put at the top of stack when defined.
     * @return the top to bottom layer sequence.
     */
    LSEQ SeqStackupTop2Bottom( PCB_LAYER_ID aSelectedLayer = UNDEFINED_LAYER ) const;

    /**
     * Return the sequence that is typical for a bottom-to-top stack-up.
     *
     * For instance, to plot multiple layers in a single image, the top layers output last.
     */
    LSEQ SeqStackupForPlotting() const;

    /**
     * Execute a function on each layer of the #LSET.
     */
    void RunOnLayers( const std::function<void( PCB_LAYER_ID )>& aFunction ) const
    {
        for( size_t ii = 0; ii < size(); ++ii )
        {
            if( test( ii ) )
                aFunction( PCB_LAYER_ID( ii ) );
        }
    }

    /**
     * Find the first set #PCB_LAYER_ID.
     *
     * @return #UNDEFINED_LAYER if more than one is set or #UNSELECTED_LAYER if none is set.
     */
    PCB_LAYER_ID ExtractLayer() const;


    /**
     * Flip the layers in this set.
     *
     * BACK and FRONT copper layers, mask, paste, solder layers are swapped
     * internal layers are flipped only if the copper layers count is known.
     *
     * @param aMask = the LSET to flip
     * @param aCopperLayersCount = the number of copper layers. if 0 (in fact if < 4 )
     *  internal layers will be not flipped because the layer count is not known
     */
    LSET& FlipStandardLayers( int aCopperLayersCount = 0 );

    /**
     * Return the number of layers between aStart and aEnd, inclusive.
     */
    static int LayerCount( PCB_LAYER_ID aStart, PCB_LAYER_ID aEnd, int aCopperLayerCount );

    /**
     * Clear the copper layers in this set.
     */
    LSET& ClearCopperLayers();

    /**
     * Clear the non-copper layers in this set.
     */
    LSET& ClearNonCopperLayers();

    /**
     * Clear the user defined layers in this set.
     */
    LSET& ClearUserDefinedLayers();

#ifndef SWIG
    // Custom iterator to iterate over all set bits
    class KICOMMON_API all_set_layers_iterator : public BASE_SET::set_bits_iterator
    {
    public:
        all_set_layers_iterator( const BASE_SET& set, size_t index ) :
                BASE_SET::set_bits_iterator( set, index )
        {
        }

        PCB_LAYER_ID operator*() const
        {
            return PCB_LAYER_ID( BASE_SET::set_bits_iterator::operator*() );
        }

        all_set_layers_iterator& operator++()
        {
            BASE_SET::set_bits_iterator::operator++();
            return *this;
        }
    };

    all_set_layers_iterator begin() const { return all_set_layers_iterator( *this, 0 ); }
    all_set_layers_iterator end() const { return all_set_layers_iterator( *this, size() ); }

    // Custom iterators for Copper and Non-Copper layers
    class KICOMMON_API copper_layers_iterator : public BASE_SET::set_bits_iterator
    {
    public:
        copper_layers_iterator( const BASE_SET& set, size_t index );
        PCB_LAYER_ID            operator*() const;
        copper_layers_iterator& operator++();

    private:
        void advance_to_next_set_copper_bit();
        void next_copper_layer();
    };

    class KICOMMON_API non_copper_layers_iterator : public BASE_SET::set_bits_iterator
    {
    public:
        non_copper_layers_iterator( const BASE_SET& set, size_t index );
        PCB_LAYER_ID                operator*() const;
        non_copper_layers_iterator& operator++();

    private:
        void advance_to_next_set_non_copper_bit();
    };

    copper_layers_iterator copper_layers_begin() const;
    copper_layers_iterator copper_layers_end() const;
    non_copper_layers_iterator non_copper_layers_begin() const;
    non_copper_layers_iterator non_copper_layers_end() const;

#endif

};
#endif // LSET_H
