/*
 * 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 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/>.
 */


/**
 * A program to test a PEGTL grammar
 */

#include <pegtl.hpp>
#include <pegtl/contrib/analyze.hpp>
#include <pegtl/contrib/parse_tree.hpp>
#include <pegtl/contrib/parse_tree_to_dot.hpp>
#include <pegtl/contrib/trace.hpp>

#include <filesystem>
#include <iostream>

using namespace tao::pegtl;

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

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

/**
 * 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<space>, one<CHAR_TO_FIND...>>{};

// Definition of "Format"
// # Format <current format number>
struct CURRENT_FORMAT_NUMBER : plus<digit> {};
struct FORMAT : seq<bol, TAO_PEGTL_STRING( "# FORMAT" ), star<space>, CURRENT_FORMAT_NUMBER>{};


// Definition of part
//.<Part name>_[(<Part number>)][:<Part version>][;<Description>]
    // string filters:
struct PART_NAME_FILTER : sor<spaced_ch<'('>, spaced_ch<':'>, spaced_ch<';'>>{};
struct PART_NUMBER_FILTER : one<')'>{};
struct PART_VERSION_FILTER : spaced_ch<';'>{};

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

    // the part itself
struct PART :   seq
                <
                    bol,
                    one<'.'>,
                    PART_NAME,
                    opt<seq<spaced_ch<'('>, PART_NUMBER, one<')'>>>,
                    opt<seq<spaced_ch<':'>, PART_VERSION>>,
                    opt<seq<spaced_ch<';'>, PART_DESCRIPTION>>
                >
{};


struct UNMATCHED_CONTENT : STRING_EXCLUDING<FORMAT, PART> {}; //@todo remove once parser is complete


struct GRAMMAR :    plus
                    <
                        sor<FORMAT, PART, UNMATCHED_CONTENT>,
                        opt<eolf>
                    >
{};

//--------------------------Simple selector definition -----------------------------
// disable all parse nodes by default
template< typename PEGTL_RULE > struct SIMPLE_SELECTOR : std::false_type {};
// enable only nodes that match certain rules:
template<> struct SIMPLE_SELECTOR< STR_SEGMENT_EXCLUDING< PART_NAME_FILTER > > : std::true_type {};
template<> struct SIMPLE_SELECTOR< STR_SEGMENT_EXCLUDING< PART_NUMBER_FILTER > > : std::true_type {};
template<> struct SIMPLE_SELECTOR< STR_SEGMENT_EXCLUDING< PART_VERSION_FILTER > > : std::true_type {};
template<> struct SIMPLE_SELECTOR< STR_SEGMENT_EXCLUDING<> > : std::true_type {};
template<> struct SIMPLE_SELECTOR< CURRENT_FORMAT_NUMBER > : std::true_type {};
template<> struct SIMPLE_SELECTOR< FORMAT > : std::true_type {};
template<> struct SIMPLE_SELECTOR< PART > : std::true_type {};
template<> struct SIMPLE_SELECTOR< PART_NAME > : std::true_type {};
template<> struct SIMPLE_SELECTOR< PART_NUMBER > : std::true_type {};
template<> struct SIMPLE_SELECTOR< PART_VERSION > : std::true_type {};
template<> struct SIMPLE_SELECTOR< PART_DESCRIPTION > : std::true_type {};
template<> struct SIMPLE_SELECTOR< UNMATCHED_CONTENT > : std::true_type {}; //@todo remove


//--------------------------Complex selector definition -----------------------------
// Helper function to clean up the tree
struct FOLD_CONTENT : parse_tree::apply<FOLD_CONTENT>
{
    template <typename Node>
    static void transform( Node& n )
    {
        if( n->children.size() == 1 )
        {
            n->children.pop_back();
        }
        else if( n->children.size() != 0 )
        {
            n->remove_content();
        }
    }
};


template <typename PEGTL_RULE>
using COMPLEX_SELECTOR = parse_tree::selector<
        PEGTL_RULE,
        parse_tree::store_content::on<
            STR_SEGMENT_EXCLUDING< PART_NAME_FILTER >,
            STR_SEGMENT_EXCLUDING< PART_NUMBER_FILTER >,
            STR_SEGMENT_EXCLUDING< PART_VERSION_FILTER >,
            STR_SEGMENT_EXCLUDING<>,
            CURRENT_FORMAT_NUMBER,
            UNMATCHED_CONTENT //@todo remove when parser complete. For debugging only
        >,
        parse_tree::remove_content::on<
            FORMAT,
            PART
        >,
        parse_tree::apply< FOLD_CONTENT >::on<
            PART_NAME,
            PART_NUMBER,
            PART_VERSION,
            PART_DESCRIPTION
        >
    >;


int main( int argc, char** argv )
{
    //  .\test_pegtl.exe complex my_file.lib | dot -Tsvg > output.svg; .\output.svg

    if( argc != 3 )
    {
        printf( "usage: %s <complex|simple> <filename>", argv[0] );
        return -1;
    }

    std::string chosen_selector = argv[1];
    std::filesystem::path filepath( argv[2] );

    file_input in( filepath );

    const std::size_t issues = tao::pegtl::analyze<GRAMMAR>();

    if( issues > 0 )
    {
        std::cout << "\n***ERROR***: " << issues << " issues found in the grammar!\n";
        return -1;
    }

    std::unique_ptr<parse_tree::node> root;

    if( chosen_selector == "complex" )
    {
        root = parse_tree::parse<GRAMMAR, COMPLEX_SELECTOR>( in );
    }
    else if( chosen_selector == "simple" )
    {
        root = parse_tree::parse<GRAMMAR, SIMPLE_SELECTOR>( in );
    }
    else
    {
        printf( "Invalid selector '%s' requested. Valid values are: complex or simple. \n",
                argv[1] );
        printf( "usage: %s <complex|simple> <filename>\n", argv[0] );
    }


    if( root )
    {
        parse_tree::print_dot( std::cout, *root );
    }
    else
    {
        std::cout << "\n***ERROR***: No root tree node!\n";

        // lets print out the trace for debugging
        standard_trace<GRAMMAR>( in );
    }

    return 0;
}