/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2014-2017  Cirilo Bernardo
 * Copyright (C) 2021 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
 */


#ifndef IDF_OUTLINES_H
#define IDF_OUTLINES_H

#include <string>
#include <list>
#include <map>

#include <idf_common.h>

/*
 *  NOTES ON OUTLINE TYPES:
 *
 *  BOARD_OUTLINE (PANEL_OUTLINE)
 *      .BOARD_OUTLINE  [OWNER]
 *      [thickness]
 *      [outlines]
 *
 *  OTHER_OUTLINE
 *      .OTHER_OUTLINE  [OWNER]
 *      [outline identifier] [thickness] [board side: Top/Bot]
 *      [outline]
 *
 *  ROUTE_OUTLINE
 *      .ROUTE_OUTLINE [OWNER]
 *      [layers]
 *      [outline]
 *
 *  PLACE_OUTLINE
 *      .PLACE_OUTLINE [OWNER]
 *      [board side: Top/Bot/Both] [height]
 *      [outline]
 *
 *  ROUTE_KEEPOUT
 *      .ROUTE_KEEPOUT [OWNER]
 *      [layers]
 *      [outline]
 *
 *  VIA_KEEPOUT
 *      .VIA_KEEPOUT [OWNER]
 *      [outline]
 *
 *  PLACE_KEEPOUT
 *      .PLACE_KEEPOUT [OWNER]
 *      [board side: Top/Bot/Both] [height]
 *      [outline]
 *
 *  Placement Group
 *      .PLACE_REGION [OWNER]
 *      [side: Top/Bot/Both ] [component group name]
 *      [outline]
 *
 *  Component Outline:
 *      .ELECTRICAL/.MECHANICAL
 *      [GEOM] [PART] [UNIT] [HEIGHT]
 *      [outline]
 *      [PROP] [prop name] [prop value]
 */

class IDF3_BOARD;


/**
 * IDFv3 BOARD OUTLINE data and is the basis of other IDFv3 outline objects.
 */
class BOARD_OUTLINE
{
public:
    BOARD_OUTLINE();
    virtual ~BOARD_OUTLINE();

    /**
     * Function SetUnit
     * sets the native unit of the outline; except for component outlines this must
     * be the same as the native unit of the parent IDF_BOARD object
     *
     * @param aUnit is the native unit (UNIT_MM or UNIT_THOU)
     */
    virtual bool SetUnit( IDF3::IDF_UNIT aUnit );

    /**
     * Return the native unit type of the outline.
     *
     * @return IDF_UNIT is the native unit (UNIT_MM or UNIT_THOU).
     */
    virtual IDF3::IDF_UNIT GetUnit( void );

    /**
     * Set the thickness or height of the outline (mm).
     *
     * @param aThickness is the thickness or height of the outline in mm.
     */
    virtual bool SetThickness( double aThickness );

    /**
     * @return the thickness or height of an outline (mm).
     */
    virtual double GetThickness( void );

    /**
     * Free memory and reinitializes all internal data except for the parent pointer.
     *
     * @return true if OK, false on ownership violations.
     */
    virtual bool Clear( void );

    /**
     * @return the type of outline according to the IDFv3 classification.
     */
    IDF3::OUTLINE_TYPE GetOutlineType( void );

    /**
     * @return the parent IDF_BOARD object.
     */
    IDF3_BOARD* GetParent( void );


    /**
     * Add the specified outline to this object.
     *
     * @param aOutline is a valid IDF outline.
     * @return true if the outline was added; false if the outline already existed or an
     *         ownership violation occurs.
     */
    bool AddOutline( IDF_OUTLINE* aOutline );

    /**
     * Remove the given outline, subject to IDF ownership rules,
     * if it is owned by this object.
     *
     * The outline pointer remains valid and it is the user's responsibility to delete the
     * object.  The first outline in the list will never be deleted unless it is the sole
     * remaining outline; this is to ensure that a board outline is not removed while the
     * cutouts remain.
     *
     * @param aOutline is a pointer to the outline to remove from the list.
     * @return true if the outline was found and removed; false if the outline was not found
     *         or an ownership violation occurs.
     */
    bool DelOutline( IDF_OUTLINE* aOutline );

    /**
     * Delete the outline specified by the given index, subject to IDF ownership rules.
     *
     * The outline data is destroyed. The first outline in the list will never be deleted
     * unless it is the sole remaining outline; this is to ensure that a board outline is
     * not removed while the cutouts remain.
     *
     * @param aIndex is an index to the outline to delete
     * @return true if the outline was found and deleted; false if the outline was not found
     *         or an ownership violation or indexation error occurs.
     */
    bool DelOutline( size_t aIndex );

    /**
     *  Return outlines list.
     *
     * It is up to the user to respect the IDFv3 specification and avoid changes to this
     * list which are in violation of the specification.
     */
    const std::list<IDF_OUTLINE*>* GetOutlines( void );

    /**
     * @return the number of items in the internal outline list.
     */
    size_t OutlinesSize( void );

    /**
     * Return a pointer to the outline as specified by aIndex.
     *
     * If the index is out of bounds NULL is returned and the error message is set. It is the
     * responsibility of the user to observe IDF ownership rules.
     */
    IDF_OUTLINE* GetOutline( size_t aIndex );

    /**
     * @return the ownership status of the outline (ECAD, MCAD, UNOWNED).
     */
    IDF3::KEY_OWNER GetOwner( void );

    /**
     * Set the ownership status of the outline subject to IDF ownership rules.
     *
     * @return true if the ownership was changed and false if a specification violation occurred.
     */
    bool SetOwner( IDF3::KEY_OWNER aOwner );

    /**
     * @return true if this type of outline only supports a single outline. All outlines except
     *         for BOARD_OUTLINE are single.
     */
    bool IsSingle( void );

    /**
     * Clear internal data except for the parent pointer.
     */
    void ClearOutlines( void );

    /**
     * Add a comment to the outline data.
     *
     * This function is not subject to IDF ownership rules.
     */
    void AddComment( const std::string& aComment );

    /**
     * @return the number of comments in the internal list.
     */
    size_t CommentsSize( void );

    /**
     * @return the internal list of comments.
     */
    std::list< std::string >* GetComments( void );

    /**
     * @return indexed comment or NULL if the index is out of bounds.
     */
    const std::string* GetComment( size_t aIndex );

    /**
     * Deletes a comment based on the given index.
     *
     * @return true if a comment was deleted, false if the index is out of bounds.
     */
    bool DeleteComment( size_t aIndex );

    /**
     * Deletes all comments.
     */
    void ClearComments( void );

    const std::string& GetError( void )
    {
        return errormsg;
    }

protected:
    // Read outline data from a BOARD or LIBRARY file's outline section
    void readOutlines( std::istream& aBoardFile, IDF3::IDF_VERSION aIdfVersion );

    // Write comments to a BOARD or LIBRARY file (must not be within a SECTION as per IDFv3 spec)
    bool writeComments( std::ostream& aBoardFile );

    // Write the outline owner to a BOARD file
    bool writeOwner( std::ostream& aBoardFile );

    // Write the data of a single outline object
    void writeOutline( std::ostream& aBoardFile, IDF_OUTLINE* aOutline, size_t aIndex );

    // Iterate through the outlines and write out all data
    void writeOutlines( std::ostream& aBoardFile );  // write outline data (no headers)

    // Clear internal list of outlines
    void clearOutlines( void );

    /**
     * Set the parent IDF_BOARD object.
     */
    void setParent( IDF3_BOARD* aParent );

    // Shadow routines used by friends to bypass ownership checks
    bool addOutline( IDF_OUTLINE* aOutline );
    virtual bool setThickness( double aThickness );
    virtual void clear( void );

    /**
     * Read data from a .BOARD_OUTLINE section.
     *
     * In case of an unrecoverable error an exception is thrown. On a successful
     * return the file pointer will be at the line following .END_BOARD_OUTLINE
     *
     * @param aBoardFile is an IDFv3 file opened for reading.
     * @param aHeader is the ".BOARD_OUTLINE" header line as read by FetchIDFLine.
     */
    virtual void readData( std::istream& aBoardFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion );

    /**
     * Write the comments and .BOARD_OUTLINE section to an IDFv3 file.
     * Throws exceptions.
     *
     * @param aBoardFile is an IDFv3 file opened for writing.
     */
    virtual void writeData( std::ostream& aBoardFile );

    std::string                 errormsg;
    std::list< IDF_OUTLINE* >   outlines;

    // indicates the owner of this outline (MCAD, ECAD, UNOWNED).
    IDF3::KEY_OWNER             owner;
    IDF3::OUTLINE_TYPE          outlineType;  // type of IDF outline
    bool                        single;       // true if only a single outline is accepted
    std::list< std::string >    comments;     // associated comment list
    IDF3::IDF_UNIT              unit;         // outline's native unit (MM or THOU)
    IDF3_BOARD*                 parent;       // BOARD which contains this outline
    double                      thickness;    // Board/Extrude Thickness or Height (IDF spec)

private:
    friend class IDF3_BOARD;
};


/**
 * Miscellaneous extrusions on the board
 */
class OTHER_OUTLINE : public BOARD_OUTLINE
{
public:
    OTHER_OUTLINE( IDF3_BOARD* aParent );

    /**
     * Function SetOutlineIdentifier
     * sets the Outline Identifier string of this OTHER_OUTLINE object
     * as per IDFv3 spec.
     */
    virtual bool SetOutlineIdentifier( const std::string& aUniqueID );

    /**
     * Function GetOutlineIdentifier
     * returns the object's Outline Identifier
     */
    virtual const std::string& GetOutlineIdentifier( void );

    /**
     * Function SetSide
     * sets the side which this outline is applicable to (TOP, BOTTOM).
     *
     * @return bool: true if the side was set, false if the side is invalid
     * or there is a violation of IDF ownership rules.
     */
    virtual bool SetSide( IDF3::IDF_LAYER aSide );

    /**
     * Function GetSide
     * returns the side which this outline is applicable to
     */
    virtual IDF3::IDF_LAYER GetSide( void );

    /**
     * Function Clear
     * deletes internal data except for the parent object
     */
    virtual bool Clear( void ) override;

private:
    /**
     * Read an OTHER_OUTLINE data from an IDFv3 file.
     * If an unrecoverable error occurs an exception is thrown.
     *
     * @param aBoardFile is an IDFv3 file open for reading.
     * @param aHeader is the .OTHER_OUTLINE header as read via FetchIDFLine.
     */
    virtual void readData( std::istream& aBoardFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion ) override;

    /**
     * Write the OTHER_OUTLINE data to an open IDFv3 file.
     *
     * @param aBoardFile is an IDFv3 file open for writing.
     * @return true if the data was successfully written, otherwise false.
     */
    virtual void writeData( std::ostream& aBoardFile ) override;

    friend class IDF3_BOARD;

    std::string uniqueID;   // Outline Identifier (IDF spec)
    IDF3::IDF_LAYER side;   // Board Side [TOP/BOTTOM ONLY] (IDF spec)
};


/**
 * Routing areas on the board.
 */
class ROUTE_OUTLINE : public BOARD_OUTLINE
{
public:
    ROUTE_OUTLINE( IDF3_BOARD* aParent );

    /**
     * Function SetLayers
     * sets the layer or group of layers this outline is applicable to.
     * This function is subject to IDF ownership rules; true is returned
     * on success, otherwise false is returned and the error message is set.
     */
    virtual bool SetLayers( IDF3::IDF_LAYER aLayer );

    /**
     * Function GetLayers
     * returns the layer or group of layers which this outline is applicable to
     */
    virtual IDF3::IDF_LAYER GetLayers( void );

    /**
     * Function Clear
     * deletes internal data except for the parent object
     */
    virtual bool Clear( void ) override;

private:
    friend class IDF3_BOARD;

    /**
     * Read ROUTE_OUTLINE data from an IDFv3 file.
     * If an unrecoverable error occurs an exception is thrown.
     *
     * @param aBoardFile is an open IDFv3 board file.
     * @param aHeader is the .ROUTE_OUTLINE header as returned by FetchIDFLine.
     */
    virtual void readData( std::istream& aBoardFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion ) override;

    /**
     * Write the ROUTE_OUTLINE data to an open IDFv3 file.
     */
    virtual void writeData( std::ostream& aBoardFile ) override;

protected:
    IDF3::IDF_LAYER layers; // Routing layers (IDF spec)
};


/**
 * Area on the board for placing components.
 */
class PLACE_OUTLINE : public BOARD_OUTLINE
{
public:
    PLACE_OUTLINE( IDF3_BOARD* aParent );

    /**
     * Function SetSide
     * sets the side (TOP, BOTTOM, BOTH) which this outline applies to.
     * This function is subject to IDF ownership rules; true is returned
     * on success, otherwise false is returned and the error message is set.
     */
    virtual bool SetSide( IDF3::IDF_LAYER aSide );

    /**
     * Function GetSide
     * returns the side which this outline is applicable to
     */
    virtual IDF3::IDF_LAYER GetSide( void );

    /**
     * Function SetMaxHeight
     * sets the maximum height of a component within this outline.
     * This function is subject to IDF ownership rules; true is returned
     * on success, otherwise false is returned and the error message is set.
     */
    virtual bool SetMaxHeight( double aHeight );

    /**
     * Function GetMaxHeight
     * returns the maximum allowable height for a component in this region
     */
    virtual double GetMaxHeight( void );

    /**
     * Function Clear
     * deletes all internal data
     */
    virtual bool Clear( void ) override;

private:
    friend class IDF3_BOARD;

    /**
     * Read PLACE_OUTLINE data from an open IDFv3 file.
     *
     * If an unrecoverable error occurs an exception is thrown.
     *
     * @param aBoardFile is an IDFv3 file opened for reading.
     * @param aHeader is the .PLACE_OUTLINE header as returned by FetchIDFLine.
     */
    virtual void readData( std::istream& aBoardFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion ) override;

    /**
     * Write the PLACE_OUTLINE data to an open IDFv3 file.
     *
     * @param aBoardFile is an IDFv3 file opened for writing.
     * @return true if the data was successfully written, otherwise false.
     */
    virtual void writeData( std::ostream& aBoardFile ) override;

protected:
    IDF3::IDF_LAYER side;   // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec)
};


/**
 * Regions and layers where no electrical routing is permitted.
 */
class ROUTE_KO_OUTLINE : public ROUTE_OUTLINE
{
public:
    ROUTE_KO_OUTLINE( IDF3_BOARD* aParent );
};


/**
 * Region in which vias are prohibited.
 *
 * @note IDFv3 only considers thru-hole vias and makes no statement regarding behavior with
 *       blind or buried vias.
 */
class VIA_KO_OUTLINE : public OTHER_OUTLINE
{
public:
    VIA_KO_OUTLINE( IDF3_BOARD* aParent );
};


/**
 * Regions and layers in which no component may be placed or on which a maximum component height
 * is in effect.
 */
class PLACE_KO_OUTLINE : public PLACE_OUTLINE
{
public:
    PLACE_KO_OUTLINE( IDF3_BOARD* aParent );
};


/**
 * Regions and layers in which user-specified features or components may be placed.
 */
class GROUP_OUTLINE : public BOARD_OUTLINE
{
public:
    GROUP_OUTLINE( IDF3_BOARD* aParent );

    /**
     * Set the side which this outline applies to (TOP, BOTTOM, BOTH).
     *
     * This function is subject to IDF ownership rules; true is returned
     * on success, otherwise false is returned and the error message is set.
     */
    virtual bool SetSide( IDF3::IDF_LAYER aSide );

    /**
     * @return the side which this outline applies to.
     */
    virtual IDF3::IDF_LAYER GetSide( void );

    /**
     * Set the name of the group, subject to IDF ownership rules.
     *
     * This function is subject to IDF ownership rules; true is returned
     * on success, otherwise false is returned and the error message is set.
     */
    virtual bool SetGroupName( std::string aGroupName );

    /**
     * Return a reference to the (non-unique) group name.
     */
    virtual const std::string& GetGroupName( void );

    /**
     * Delete internal data, subject to IDF ownership rules.
     */
    virtual bool Clear( void ) override;

private:
    friend class IDF3_BOARD;

    /**
     * Read GROUP_OUTLINE data from an open IDFv3 file.
     *
     * If an unrecoverable error occurs an exception is thrown.
     *
     * @param aBoardFile is an open IDFv3 file.
     * @param aHeader is the .PLACE_REGION header as returned by FetchIDFLine.
     */
    virtual void readData( std::istream& aBoardFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion ) override;

    /**
     * Write the data to a .PLACE_REGION section of an IDFv3 file.
     *
     * @param aBoardFile is an IDFv3 file open for writing.
     * @return true if the data is successfully written, otherwise false.
     */
    virtual void writeData( std::ostream& aBoardFile ) override;

    IDF3::IDF_LAYER side;   // Board Side [TOP/BOTTOM/BOTH ONLY] (IDF spec)
    std::string groupName;  // non-unique string
};


/**
 * A component's outline as stored in an IDF library file.
 */
class IDF3_COMP_OUTLINE : public BOARD_OUTLINE
{
public:
    IDF3_COMP_OUTLINE( IDF3_BOARD* aParent );

    /**
     * Delete internal outline data.
     */
    virtual bool Clear( void ) override;

    /**
     * Set the type of component outline (.ELECTRICAL or .MECHANICAL).
     *
     * @return true on success, otherwise false and the error message is set.
     */
    bool SetComponentClass( IDF3::COMP_TYPE aCompClass );

    /**
     * @return2 the class of component represented by this outline.
     */
    IDF3::COMP_TYPE GetComponentClass( void );

    /**
     * Set the Geometry Name (Package Name, IDFv3 spec) of the component outline.
     */
    void SetGeomName( const std::string& aGeomName );

    /**
     * @return the Geometry Name (Package Name) of the component outline.
     */
    const std::string& GetGeomName( void );

    /**
     * Set the Part Name (Part Number, IDFv3 spec) of the component outline.
     */
    void SetPartName( const std::string& aPartName );

    /**
     * Return the Part Name (Part Number) of the component outline.
     */
    const std::string& GetPartName( void );

    /**
     * @return the unique identifier for this component outline, this is equal to
     *         GEOM_NAME + "_" + PART_NAME.
     */
    const std::string& GetUID( void );

    /**
     * Create a default outline with the given Geometry and Part names.
     *
     * This outline is a star with outer radius 5mm and inner radius 2.5mm.
     */
    bool CreateDefaultOutline( const std::string &aGeom, const std::string &aPart );

    // XXX: property manipulators

private:
    friend class IDF3_BOARD;
    friend class IDF3_COMP_OUTLINE_DATA;

    void readProperties( std::istream& aLibFile );
    bool writeProperties( std::ostream& aLibFile );

    /**
     * Read a component outline from an open IDFv3 file.
     *
     * If an unrecoverable error occurs, an exception is thrown.
     *
     * @param aLibFile is an open IDFv3 Library file.
     * @param aHeader is the .ELECTRICAL or .MECHANICAL header as returned by FetchIDFLine.
     */
    virtual void readData( std::istream& aLibFile, const std::string& aHeader,
                           IDF3::IDF_VERSION aIdfVersion ) override;

    /**
     * Write comments and component outline data to an IDFv3 Library file.
     *
     * @param aLibFile is an IDFv3 library file open for writing.
     * @return true if the data was successfully written, otherwise false.
     */
    virtual void writeData( std::ostream& aLibFile ) override;

    /**
     * Increment the internal reference counter to keep track of the number of
     * components referring to this outline.
     *
     * @return the number of current references to this component outline.
     */
    int incrementRef( void );

    /**
     * Decrement the internal reference counter to keep track of the number of
     * components referring to this outline.
     *
     * @return the number of remaining references or -1 if there were no references when the
     *         function was invoked, in which case the error message is also set.
     */
    int decrementRef( void );

    std::string     uid;        // unique ID
    std::string     geometry;   // geometry name (IDF)
    std::string     part;       // part name (IDF)
    IDF3::COMP_TYPE compType;   // component type
    int             refNum;     // number of components referring to this outline

    std::map< std::string, std::string >    props;      // properties list
};

#endif // IDF_OUTLINES_H
