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

#include <cctype>
#include <iostream>
#include <sstream>

#include <idf_common.h>
#include <idf_helpers.h>

using namespace std;
using namespace IDF3;


bool IDF3::FetchIDFLine( std::istream& aModel, std::string& aLine, bool& isComment,
                         std::streampos& aFilePos )
{
    aLine = "";
    aFilePos = aModel.tellg();

    if( aModel.fail() )
        return false;

    std::getline( aModel, aLine );

    isComment = false;

    // A comment begins with a '#' and must be the first character on the line
    if( aLine[0] == '#' )
    {
        // opening '#' is stripped
        isComment = true;
        aLine.erase( aLine.begin() );
    }

    // strip leading and trailing spaces
    while( !aLine.empty() && isspace( *aLine.begin() ) )
        aLine.erase( aLine.begin() );

    while( !aLine.empty() && isspace( *aLine.rbegin() ) )
        aLine.erase( --aLine.end() );

    // a comment line may be empty to improve human readability
    if( aLine.empty() && !isComment )
        return false;

    return true;
}


bool IDF3::GetIDFString( const std::string& aLine, std::string& aIDFString, bool& hasQuotes,
                         int& aIndex )
{
    // 1. drop all leading spaces
    // 2. if the first character is '"', read until the next '"',
    //    otherwise read until the next space or EOL.

    std::ostringstream ostr;

    int len = aLine.length();
    int idx = aIndex;

    if( idx < 0 || idx >= len )
        return false;

    while( idx < len && isspace( aLine[idx] ) )
        ++idx;

    if( idx == len )
    {
        aIndex = idx;
        return false;
    }

    if( aLine[idx] == '"' )
    {
        hasQuotes = true;
        ++idx;

        while( idx < len && aLine[idx] != '"' )
            ostr << aLine[idx++];

        if( idx == len )
        {
            ERROR_IDF << "unterminated quote mark in line:\n";
            aIndex = idx;
            return false;
        }

        ++idx;
    }
    else
    {
        hasQuotes = false;

        while( idx < len && !isspace( aLine[idx] ) )
            ostr << aLine[idx++];

    }

    aIDFString = ostr.str();
    aIndex = idx;

    return true;
}


// perform a comparison between a fixed token string and an input string.
// the token is assumed to be an upper case IDF token and the input string
// is data from an IDF file. Since IDF tokens are case-insensitive, we cannot
// assume anything about the case of the input string.
bool IDF3::CompareToken( const char* aTokenString, const std::string& aInputString )
{
    std::string::size_type i, j;
    std::string bigToken = aInputString;
    j = aInputString.length();

    for( i = 0; i < j; ++i )
        bigToken[i] = std::toupper( bigToken[i] );

    if( !bigToken.compare( aTokenString ) )
        return true;

    return false;
}


bool IDF3::ParseOwner( const std::string& aToken, IDF3::KEY_OWNER& aOwner )
{
    if( CompareToken( "UNOWNED", aToken ) )
    {
        aOwner = UNOWNED;
        return true;
    }
    else if( CompareToken( "ECAD", aToken ) )
    {
        aOwner = ECAD;
        return true;
    }
    else if( CompareToken( "MCAD", aToken ) )
    {
        aOwner = MCAD;
        return true;
    }

    ERROR_IDF << "unrecognized IDF OWNER: '" << aToken << "'\n";

    return false;
}


bool IDF3::ParseIDFLayer( const std::string& aToken, IDF3::IDF_LAYER& aLayer )
{
    if( CompareToken( "TOP", aToken ) )
    {
        aLayer = LYR_TOP;
        return true;
    }
    else if( CompareToken( "BOTTOM", aToken ) )
    {
        aLayer = LYR_BOTTOM;
        return true;
    }
    else if( CompareToken( "BOTH", aToken ) )
    {
        aLayer = LYR_BOTH;
        return true;
    }
    else if( CompareToken( "INNER", aToken ) )
    {
        aLayer = LYR_INNER;
        return true;
    }
    else if( CompareToken( "ALL", aToken ) )
    {
        aLayer = LYR_ALL;
        return true;
    }

    ERROR_IDF << "unrecognized IDF LAYER: '" << aToken << "'\n";

    aLayer = LYR_INVALID;
    return false;
}


bool IDF3::WriteLayersText( std::ostream& aBoardFile, IDF3::IDF_LAYER aLayer )
{
    switch( aLayer )
    {
    case LYR_TOP:
        aBoardFile << "TOP";
        break;

    case LYR_BOTTOM:
        aBoardFile << "BOTTOM";
        break;

    case LYR_BOTH:
        aBoardFile << "BOTH";
        break;

    case LYR_INNER:
        aBoardFile << "INNER";
        break;

    case LYR_ALL:
        aBoardFile << "ALL";
        break;

    default:
        do
        {
            std::ostringstream ostr;
            ostr << "invalid IDF layer: " << aLayer;

            throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
        } while( 0 );

        break;
    }

    return !aBoardFile.fail();
}


std::string IDF3::GetPlacementString( IDF3::IDF_PLACEMENT aPlacement )
{
    switch( aPlacement )
    {
    case PS_UNPLACED:
        return "UNPLACED";

    case PS_PLACED:
        return "PLACED";

    case PS_MCAD:
        return "MCAD";

    case PS_ECAD:
        return "ECAD";

    default:
        break;
    }

    std::ostringstream ostr;
    ostr << "[INVALID PLACEMENT VALUE]:" << aPlacement;

    return ostr.str();
}


std::string IDF3::GetLayerString( IDF3::IDF_LAYER aLayer )
{
    switch( aLayer )
    {
    case LYR_TOP:
        return "TOP";

    case LYR_BOTTOM:
        return "BOTTOM";

    case LYR_BOTH:
        return "BOTH";

    case LYR_INNER:
        return "INNER";

    case LYR_ALL:
        return "ALL";

    default:
        break;
    }

    std::ostringstream ostr;
    ostr << "[INVALID LAYER VALUE]:" << aLayer;

    return ostr.str();
}


std::string IDF3::GetOwnerString( IDF3::KEY_OWNER aOwner )
{
    switch( aOwner )
    {
    case IDF3::UNOWNED:
        return "UNOWNED";

    case IDF3::MCAD:
        return "MCAD";

    case IDF3::ECAD:
        return "ECAD";

    default:
        break;
    }

    ostringstream ostr;
    ostr << "UNKNOWN: " << aOwner;

    return ostr.str();
}
