/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2022 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
 * Copyright (C) 2022-2023 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/>.
 */

#include <pegtl.hpp>

namespace CADSTAR_PARTS_LIB
{
using namespace tao::pegtl;

//-------------------- Grammar definition ----------------------------------------------------

/**
 * Needed, because PEGTL "space" includes newline characters
 */
struct WHITESPACE : one<' ', '\t'>{};

/**
 * Empty line with whitespaces
 */
struct EMPTY_LINE : seq< bol, star<WHITESPACE>, eol>{};

/**
 * Any text in the format can span multiple lines using '&'
 */
struct LINE_CONTINUATION : seq<one<'&'>, eol>{};


struct WHITESPACE_OR_CONTINUATION : sor<WHITESPACE, LINE_CONTINUATION> {};

/**
 * String segment( no line continuation ), with exclusion rules
 */
template <typename... EXCLUSION_RULES>
struct STR_SEGMENT_EXCLUDING : plus<not_at<sor<eolf, LINE_CONTINUATION, EXCLUSION_RULES...>>, any>{};

/**
 * String with optional line continuation and exclusion rules
 */
template <typename... EXCLUSION_RULES>
struct STRING_EXCLUDING : plus<STR_SEGMENT_EXCLUDING<EXCLUSION_RULES...>, opt<LINE_CONTINUATION>> {};


/**
 * Control character with or without preceding whitespace
 */
template <char... CHAR_TO_FIND>
struct spaced_ch : seq<star<WHITESPACE>, one<CHAR_TO_FIND...>>{};

/**
 * String inside quotation marks
 */
struct QUOTED_STRING : seq<one<'"'>, STRING_EXCLUDING<one<'"'>>, one<'"'>> {};

/**
 * String inside single quotation marks
 */
struct SINGLE_QUOTED_STRING : seq<one<'\''>, STRING_EXCLUDING<one<'\''>>, one<'\''>> {};


/**
 * String inside brackets with preceding spaces
 */
struct STRING_IN_BRACKETS :
                seq
                <
                    spaced_ch<'('>,
                    sor<
                        QUOTED_STRING,
                        STRING_EXCLUDING<one<')'>>
                    >,
                    one<')'>
                >
{};

/**
 * String inside brackets with preceding spaces, ending with EOL or EOF
 */
struct STRING_IN_BRACKETS_EOLF :
                seq
                <
                    spaced_ch<'('>,
                    sor<
                        QUOTED_STRING,
                        STRING_EXCLUDING<seq<one<')'>, eolf>>
                    >,
                    seq<one<')'>, eolf>
                >
{};


// **************
// * FORMAT     *
// **************

// Definition of "Format"
// # FORMAT <current format number>
struct CURRENT_FORMAT_NUMBER : plus<digit> {};
struct FORMAT : seq
                <
                    bol,
                    one<'#'>,
                    star<WHITESPACE>,
                    TAO_PEGTL_ISTRING( "FORMAT" ),
                    star<WHITESPACE>,
                    CURRENT_FORMAT_NUMBER,
                    opt<eol>
                >
 {};


// Newer Parts files have possibility of specifying a tree-like structure to show hierarchy
//
// Example:
//+N0 'root' &
//'part1' 'part2'
//+N1 N0 'subnode1' 'part3' 'part4'
struct HIERARCHY_NODE_INDEX :   plus<digit>{};
struct HIERARCHY_CURRENT_NODE : seq<one<'N'>, HIERARCHY_NODE_INDEX>{};
struct HIERARCHY_PARENT_NODE :  seq<one<'N'>, HIERARCHY_NODE_INDEX>{}; // Different action
struct HIERARCHY_NODE_NAME :    SINGLE_QUOTED_STRING {};
struct HIERARCHY_PART_NAME :    SINGLE_QUOTED_STRING {};
struct HIERARCHY_NODE_ENTRY :
                seq
                <
                    bol,
                    one<'+'>,
                    HIERARCHY_CURRENT_NODE,     // N1
                    plus<WHITESPACE_OR_CONTINUATION>,
                    opt<HIERARCHY_PARENT_NODE>, // N0
                    star<WHITESPACE_OR_CONTINUATION>,
                    HIERARCHY_NODE_NAME,       // 'subnode1'
                    star<WHITESPACE_OR_CONTINUATION>,
                    star<HIERARCHY_PART_NAME, star<WHITESPACE_OR_CONTINUATION>>, // 'part1' 'part2'
                    opt<eol>
                >
 {};

// **************
// * PART ENTRY *
// **************

// Part Header
// -----------
//.<Part name>_[(<Part number>)][:<Part version>][;<Description>]

    // string filters:
struct PART_HEADER_START : one <'.'>{};
struct PART_NAME_FILTER : sor<spaced_ch<'('>, spaced_ch<':'>, spaced_ch<';'>>{};
struct PART_NUMBER_FILTER : one<')'>{};
struct PART_VERSION_FILTER : spaced_ch<';'>{};

    // part header elements:
struct PART_NAME : STRING_EXCLUDING<PART_NAME_FILTER> {};
struct PART_NUMBER : STRING_IN_BRACKETS {};
struct PART_VERSION : STRING_EXCLUDING<PART_VERSION_FILTER> {};
struct PART_DESCRIPTION : STRING_EXCLUDING<> {};

struct PART_HEADER :
                seq
                <
                    bol,
                    one<'.'>,
                    must<PART_NAME>,
                    opt<PART_NUMBER>,
                    opt<seq<spaced_ch<':'>, PART_VERSION>>,
                    opt<seq<spaced_ch<';'>, PART_DESCRIPTION>>,
                    opt<eol>
                >
{};

// Part - PCB Component
// --------------------
//<PCB Component Refname>[_(<PCB Alternate Refname>)]

    // string filters:
struct PCB_COMPONENT_FILTER : spaced_ch<'('>{};
struct PCB_ALTERNATE_FILTER : one<')'>{};

    // pcb component elements
struct PCB_COMPONENT : STRING_EXCLUDING<PCB_COMPONENT_FILTER> {};
struct PCB_ALTERNATE : STRING_IN_BRACKETS {};

struct PART_PCB_COMPONENT :
                seq
                <
                    bol,
                    PCB_COMPONENT,
                    opt<PCB_ALTERNATE>,
                    opt<eol>
                >
{};

// Part Value
// -----------
//[*VALUE_<Value>]
struct VALUE : STRING_EXCLUDING<> {};
struct PART_VALUE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*VALUE"),
                    plus<WHITESPACE>,
                    VALUE,
                    opt<eol>
                >
{};

struct PINNUM : plus<digit> {};

// Pin Names
//[*PNM_<Pinnum>=<Pinname>[_<Pinnum>=<Pinname>]_etc]
// Maximum 10 characters allowed for Pinname according to documentation
struct PINNAME : rep_min_max<1,10,alnum> {};
struct PINNAME_ENTRY :
                seq
                <
                    plus<WHITESPACE_OR_CONTINUATION>,
                    PINNUM,
                    one<'='>,
                    PINNAME
                >
{};

struct PIN_NAMES_LIST :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*PNM"),
                    plus<PINNAME_ENTRY>,
                    opt<eol>
                >
{};

// Pin Labels
//[*PLB_<Pinnum>=<Pinlabel>[_<Pinnum>=<Pinlabel>]_etc]
struct PINLABEL : sor<QUOTED_STRING, STRING_EXCLUDING<WHITESPACE>> {};
struct PINLABEL_ENTRY :
                seq
                <
                    plus<WHITESPACE_OR_CONTINUATION>,
                    PINNUM,
                    one<'='>,
                    PINLABEL
                >
{};

struct PIN_LABELS_LIST :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*PLB"),
                    plus<PINLABEL_ENTRY>,
                    opt<eol>
                >
{};

// Pin Equivalences
//[*EQU_<PinIdentifier>=<PinIdentifier>[=<PinIdentifier>=<PinIdentifier>_etc ...]]
struct EQUIVALENT_PIN : PINNUM {}; // same grammar but different action to be applied
struct EQUIVALENT_PINS_GROUP :
                seq
                <
                    plus<WHITESPACE_OR_CONTINUATION>,
                    EQUIVALENT_PIN,
                    plus<one<'='>, star<WHITESPACE_OR_CONTINUATION>, EQUIVALENT_PIN>
                >
{};

struct PIN_EQUIVALENCES :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*EQU"),
                    EQUIVALENT_PINS_GROUP,
                    star<one<','>, EQUIVALENT_PINS_GROUP>,
                    opt<eol>
                >
{};


// INTERNAL AND EXTERNAL PIN SWAPPING
// *SYM_[<Element name>]
struct SYM_ELEMENT_NAME : STRING_EXCLUDING<> {};
struct SYM_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*SYM"),
                    star<WHITESPACE>,
                    opt<SYM_ELEMENT_NAME>,
                    opt<eol>
                >
{};

struct GATE_PINS_LIST :
                seq
                <
                    plus<WHITESPACE>,
                    EQUIVALENT_PIN,
                    star
                    <
                        plus<WHITESPACE_OR_CONTINUATION>,
                        EQUIVALENT_PIN
                    >
                >
{};

//[*INT_<Pinnum>_[<Pinnum>_ etc ...]]
struct INTERNAL_SWAP_GATE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*INT"),
                    GATE_PINS_LIST,
                    opt<eol>
                >
{};

//[*EXT_<Pinnum>_[<Pinnum>_ etc ...]]
struct EXTERNAL_SWAP_GATE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*EXT"),
                    GATE_PINS_LIST,
                    opt<eol>
                >
{};

// Internal swapping group E.g.:
//*SYM SYM1
//*INT 2 3
//*INT 4 5
struct INTERNAL_SWAP_GROUP :
                seq
                <
                    SYM_LINE,
                    plus<INTERNAL_SWAP_GATE>
                >
{};

// External swapping group E.g.:
//*SYM SYM1
//*EXT 2 3
//*EXT 4 5
struct EXTERNAL_SWAP_GROUP :
                seq
                <
                    SYM_LINE,
                    plus<EXTERNAL_SWAP_GATE>
                >
{};

// Part Definition
// -----------
//[*DFN_<Definition name>]
struct DEFINITION_NAME : STRING_EXCLUDING<> {};
struct DFN_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*DFN"),
                    plus<WHITESPACE>,
                    DEFINITION_NAME,
                    opt<eol>
                >
{};

//[*NGS]
struct NGS_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*NGS"),
                    opt<eol>
                >
{};

//[*NPV]
struct NPV_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*NPV"),
                    opt<eol>
                >
{};

//[*STM_<Component name stem>]
struct STEM : STRING_EXCLUDING<> {};
struct STM_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*STM"),
                    plus<WHITESPACE>,
                    STEM,
                    opt<eol>
                >
{};

//[*MXP <Maximum number of connector pins>]
struct MAX_PIN_COUNT : plus<digit> {};
struct MXP_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*MXP"),
                    plus<WHITESPACE>,
                    MAX_PIN_COUNT,
                    opt<eol>
                >
{};

//[*SPI_[(<Part name>)]_[<Model>]_<Component Value>]
struct SPICE_PART_NAME : STRING_IN_BRACKETS {};
struct SPICE_MODEL : sor<QUOTED_STRING, STRING_EXCLUDING<>> {};
struct SPI_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*SPI"),
                    plus<WHITESPACE>,
                    opt<SPICE_PART_NAME>,
                    star<WHITESPACE>,
                    SPICE_MODEL,
                    opt<eol>
                >
{};


//[*PAC_(<Part name>)_<Acceptance Text>]
struct ACCEPTANCE_PART_NAME : STRING_IN_BRACKETS {};
struct ACCEPTANCE_TEXT : STRING_EXCLUDING<> {};
struct PAC_LINE :
                seq
                <
                    bol,
                    TAO_PEGTL_ISTRING( "*PAC"),
                    plus<WHITESPACE>,
                    opt<ACCEPTANCE_PART_NAME>,
                    plus<WHITESPACE>,
                    ACCEPTANCE_TEXT,
                    opt<eol>
                >
{};

// User defined part attributes
// -----------
//[*<User-defined name>_<Value>]
struct USER_PART_ATTRIBUTE_NAME : sor<QUOTED_STRING, STRING_EXCLUDING<WHITESPACE>> {};
struct USER_PART_ATTRIBUTE_VALUE : STRING_EXCLUDING<> {};
struct USER_PART_ATTRIBUTE :
                seq
                <
                    bol,
                    one<'*'>,
                    USER_PART_ATTRIBUTE_NAME,
                    plus<WHITESPACE>,
                    USER_PART_ATTRIBUTE_VALUE,
                    opt<eol>
                >
{};

//----------------------------------------------------
// In-built attributes: schematic, PCB, both (sch+pcb) and parts
//----------------------------------------------------
struct READONLY : one <'!'>{};
struct ATTRIBUTE_NAME : sor<QUOTED_STRING, STRING_EXCLUDING< spaced_ch<'('>>> {};
struct ATTRIBUTE_VALUE : STRING_IN_BRACKETS {};
struct ATTRIBUTE_VALUE_EOLF : STRING_IN_BRACKETS_EOLF {};

template<char START_TOKEN>
struct GENERIC_ATTRIBUTE :
                seq
                <
                    bol,
                    one<START_TOKEN>,
                    opt<READONLY>,
                    ATTRIBUTE_NAME,
                    ATTRIBUTE_VALUE_EOLF
                >
{};

//[$[!]<SCM Attribute name>(<Attribute value>)]
struct SCM_ATTRIBUTE : GENERIC_ATTRIBUTE<'$'>{};


//[%[!]<PCB Attribute name>(<Attribute value>)]
struct PCB_ATTRIBUTE : GENERIC_ATTRIBUTE<'%'>{};


//[~[!]<Parts Library Attribute Name>(<Attribute Value>)]
struct PART_ATTRIBUTE : GENERIC_ATTRIBUTE<'~'>{};


//[@[!]<SCM/PCB Attribute name>(<Attribute value>)]
struct SCH_PCB_ATTRIBUTE : GENERIC_ATTRIBUTE<'@'>{};


//[<SCM Symbol Refname>][_(<SCM Alternate Refname>)]
struct SCH_NAME : sor<QUOTED_STRING, STRING_EXCLUDING<spaced_ch<'('>>> {};
struct SCH_ALTERNATE : STRING_IN_BRACKETS {};
struct SCH_SYMBOL_LINE : seq<SCH_NAME, opt<SCH_ALTERNATE>, opt<eol>>{};

//[<PinIdentifier>[.<Position>] [!<Pintype>] [:<Loading>]]
struct PIN_IDENTIFIER : plus<digit>{};
struct PIN_POSITION : range<'0', '3'>{};
struct PIN_TYPE : star<alpha>{};
struct PIN_LOADING : plus<digit>{};

struct PIN_ENTRY :
                seq
                <
                    PIN_IDENTIFIER,
                    one<'.'>,
                    PIN_POSITION,
                    opt< one<'!'>, PIN_TYPE>,
                    opt< one<':'>, PIN_LOADING>
                >
{};

struct PIN_LIST : plus<PIN_ENTRY, star<WHITESPACE>, opt<LINE_CONTINUATION>> {};

struct SYMBOL_ENTRY : seq<SCH_SYMBOL_LINE, PIN_LIST, opt<eol>>{};


// /<Signame>_<PinIdentifier>[.<Position>][!<Pintype>][:<Loading>]
struct PIN_SIGNAL_NAME : seq<one<'/'>, STRING_EXCLUDING<WHITESPACE>> {};
struct HIDDEN_PIN_ENTRY : seq<PIN_SIGNAL_NAME, plus<WHITESPACE>, PIN_LIST, opt<eol>>{};


//******************
// Join all together

struct PART_ENTRY :
                seq
                <
                    PART_HEADER,               //.<Part name>[ (1234): 1 ;<Description>]
                    PART_PCB_COMPONENT,        //<PCB Component Refname> [(Alternate)]

                    // In any order:
                    star<sor<
                        PART_VALUE,            //[*VALUE <Value>]
                        PIN_NAMES_LIST,        //[*PNM <ID><Name>[ <ID><Name>] ...]
                        PIN_LABELS_LIST,       //[*PLB <ID><Label>[ <ID><Label>] ...]
                        PIN_EQUIVALENCES,      //[*EQU_<ID>=<ID>[=<ID>=<ID>_etc ...]]
                        INTERNAL_SWAP_GROUP,   //[*SYM SYM1  |*INT 2 3 |*INT 4 5]
                        EXTERNAL_SWAP_GROUP,   //[*SYM SYM1  |*EXT 2 3 |*EXT 4 5]
                        DFN_LINE,              //[*DFN_<Definition name>]
                        NGS_LINE,              //[*NGS]
                        NPV_LINE,              //[*NPV]
                        STM_LINE,              //[*STM_<Component name stem>]
                        MXP_LINE,              //[*MXP <Maximum number of connector pins>]
                        SPI_LINE,              //[*SPI_[(<Part name>)]_[<Model>]_<Component Value>]
                        PAC_LINE,              //[*PAC_(<Part name>)_<Acceptance Text>]
                        USER_PART_ATTRIBUTE,   //[*<User-defined name>_<Value>]
                        SCM_ATTRIBUTE,         //[$[!]<SCM Attribute name>(<Attribute value>)]
                        PCB_ATTRIBUTE,         //[%[!]<PCB Attribute name>(<Attribute value>)]
                        PART_ATTRIBUTE,        //[~[!]<Parts Library Attribute Name>(<Attribute Value>)]
                        SCH_PCB_ATTRIBUTE      //[@[!]<SCM/PCB Attribute name>(<Attribute value>)]
                    >>,
                    star<SYMBOL_ENTRY>,        //[<SCM Symbol Refname>][_(<SCM Alternate Refname>)]
                                               //[Pin entry] [Pin entry] ...

                    star<HIDDEN_PIN_ENTRY>     //[/<Signame>_<Pin entry>]

                >
{};


/**
 * Grammar for CADSTAR Parts Library file format (*.lib)
 */
struct GRAMMAR :
                must<
                    opt<FORMAT>,
                    star<star<EMPTY_LINE>, HIERARCHY_NODE_ENTRY>,
                    plus
                    <
                        sor
                        <
                            PART_ENTRY,
                            EMPTY_LINE // optional empty line
                        >,
                        opt<eol>
                    >,
                    opt<TAO_PEGTL_ISTRING( ".END"), opt<eol>>,
                    star<EMPTY_LINE>,
                    tao::pegtl::eof // just putting "eof" results in ambiguous symbol
                >
{};


/**
 * Grammar to parse the file header only.
 * In general a valid file should have `#FORMAT 32` in the first line but there appear to be some
 * files that ommit the format specifier and start straight away with the part definitions. Just
 * in case, we will also allow the first part to be up to 5 lines into the file (arbitrary number
 * just to limit the time spent in reading a file header to determine whether it is valid).
 */
struct VALID_HEADER : sor<FORMAT, seq< rep_max<5,EMPTY_LINE>, PART_HEADER>>{};

} // namespace CADSTAR_PART_LIB
