/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2013-2020 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
 */

// Something in either <boost/property_tree/ptree.hpp> causes a bunch of compiler
// errors in <wx/msw/winundef.h> version 2.9 on MinGW.
#include <macros.h>

#include <boost/property_tree/ptree.hpp>

#include <cassert>
#include <ptree.h>

typedef PTREE::const_iterator           CITER;
typedef PTREE::iterator                 ITER;

#if defined(DEBUG)
 #define D(x)   x
#else
 #define D(x)
#endif

#define CTL_OMIT_NL                     (1<<0)
#define CTL_IN_ATTRS                    (1<<1)


//-----<Scan>------------------------------------------------------------------

/**
 * Function scanList
 * reads a sexpr list from the input stream into a new node with key
 * aLexer->CurText().
 */
inline void scanList( PTREE* aTree, DSNLEXER* aLexer )
{
    assert( aLexer->CurTok() == DSN_LEFT );

    int tok = aLexer->NextTok();

    const char* key = aLexer->CurText();

    PTREE* list = &aTree->push_back( PTREE::value_type( key, PTREE() ) )->second;

    if( tok != DSN_RIGHT )
    {
        while( ( tok = aLexer->NextTok() ) != DSN_RIGHT )
        {
            if( tok == DSN_EOF )
                aLexer->Unexpected( DSN_EOF );

            Scan( list, aLexer );
        }
    }
}


inline void scanAtom( PTREE* aTree, const DSNLEXER* aLexer )
{
    const char* key = aLexer->CurText();

    aTree->push_back( PTREE::value_type( key, PTREE() ) );
}


void Scan( PTREE* aTree, DSNLEXER* aLexer )
{
    int tok  = aLexer->CurTok();

    // conditionally read first token.
    if( tok == DSN_NONE )
        tok = aLexer->NextTok();

    if( tok == DSN_EOF )
    {
        aLexer->Unexpected( DSN_EOF );
    }

    if( tok == DSN_LEFT )
    {
        scanList( aTree, aLexer );
    }
    else
    {
        scanAtom( aTree, aLexer );
    }
}


//-----<Format>------------------------------------------------------------------

inline bool isAtom( const CPTREE& aTree )
{
    return aTree.size() == 0 && aTree.data().size() == 0;
}


inline bool isLast( const CPTREE& aTree, CITER it )
{
    CITER next = it;
    ++next;
    return next == aTree.end();
}


inline CITER next( CITER it )
{
    CITER n = it;
    return ++n;
}


static void formatNode( OUTPUTFORMATTER* out, int aNestLevel, int aCtl,
        const std::string& aKey, const CPTREE& aTree );


static void formatList( OUTPUTFORMATTER* out, int aNestLevel, int aCtl, const CPTREE& aTree )
{
    for( CITER it = aTree.begin(); it != aTree.end(); ++it )
    {
        // Processing a tree which was read in with xml_parser?
        if( it->first == "<xmlattr>" )
        {
            formatList( out, aNestLevel, aCtl | CTL_IN_ATTRS, it->second );
            continue;
        }

        int ctl = 0;

        if( isLast( aTree, it ) )   // is "it" the last one?
        {
            //if( !( aCtl & CTL_IN_ATTRS ) )
                ctl = CTL_OMIT_NL;
        }
        else if( isAtom( next( it )->second ) )
        {
            /* if( !( aCtl & CTL_IN_ATTRS ) ) */
                ctl = CTL_OMIT_NL;
        }

        formatNode( out, aNestLevel+1, ctl, it->first, it->second );
    }
}


static void formatNode( OUTPUTFORMATTER* out, int aNestLevel, int aCtl,
        const std::string& aKey, const CPTREE& aTree )

{
    if( !isAtom( aTree ) )     // is a list, not an atom
    {
        int ctl = CTL_OMIT_NL;

        // aTree is list and its first child is a list
        if( aTree.size() && !isAtom( aTree.begin()->second ) && !aTree.data().size() )
            ctl = 0;

        out->Print( aNestLevel, "(%s%s", out->Quotes( aKey ).c_str(), ctl & CTL_OMIT_NL ? "" : "\n" );

        if( aTree.data().size() )       // sexpr format does not use data()
        {
            out->Print( 0, " %s%s",
                out->Quotes( aTree.data() ).c_str(),
                aTree.size() ? "\n" : ""
                );
        }

        formatList( out, aNestLevel, aCtl, aTree );

        out->Print( 0, ")%s", aCtl & CTL_OMIT_NL ? "" : "\n" );
    }

    else            // is an atom, not a list
    {
        out->Print( 0, " %s", out->Quotes( aKey ).c_str() );
    }
}


void Format( OUTPUTFORMATTER* out, int aNestLevel, int aCtl, const CPTREE& aTree )
{
    if( aTree.size() == 1 && !aTree.data().size() )
    {
        // The topmost node is basically only a container for the document root.
        // It anchors the paths which traverse the tree deeper.
        CITER   it = aTree.begin();
        formatNode( out, aNestLevel, aCtl, it->first, it->second );
    }
    else
    {
        // This is not expected, neither for sexpr nor xml.
        formatNode( out, aNestLevel, aCtl, "", aTree );
    }
}