/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#include <precomp.h>
#include <cosv/commandline.hxx>

// NOT FULLY DECLARED SERVICES
#include <cosv/file.hxx>


namespace csv
{

namespace
{

const intt C_nNoOption = -1;

const char * sIncludeOptionShort = "-A:";
const char * sIncludeOptionLong  = "--Arguments:";
const uintt nIncludeOptionShort_Length = strlen(sIncludeOptionShort);
const uintt nIncludeOptionLong_Length = strlen(sIncludeOptionLong);


/** Analyses, if an option is the one to include a file with
    further command line arguments.
*/
bool                IsIncludeOption(
                        const String &      i_option );

/** Gets the file name from an include-arguments-option.
*/
String              IncludeFile_fromIncludeOption(
                        const String &      i_option );


bool
IsIncludeOption(const String & i_option)
{
    return strncmp(i_option, sIncludeOptionShort, nIncludeOptionShort_Length) == 0
           OR
           strncmp(i_option, sIncludeOptionLong, nIncludeOptionLong_Length) == 0;
}

String
IncludeFile_fromIncludeOption(const String & i_option)
{
    if ( strncmp(i_option, sIncludeOptionShort, nIncludeOptionShort_Length)
         == 0 )
    {
        return String(i_option, nIncludeOptionShort_Length, str::maxsize);
    }
    else
    if ( strncmp(i_option, sIncludeOptionLong, nIncludeOptionLong_Length)
         == 0 )
    {
        return String(i_option, nIncludeOptionLong_Length, str::maxsize);
    }
    return String::Null_();
}


}   // end anonymous namespace




/** Local helper class for searching a possible option name in a vector of
    ->OptionDescription.
*/
struct CommandLine::
FindOptionByText
{
    bool                operator()(
                            const CommandLine::OptionDescription &
                                                i_option )
                        { return i_option.sText == sOption; }

    /// @param i_searchText [i_searchText != ""]
                        FindOptionByText(
                            const String &      i_option )
                        :   sOption(i_option)   { }
  private:
    const String        sOption;
};


typedef std::vector<StringVector::const_iterator>   StringCIteratorList;
typedef std::vector<intt>                           OptionIdList;

bool
CommandLine::Interpret( int    argc,
                        char * argv[] )
{
    Get_Arguments(argc,argv);
    csv_assert(aOptionPoints.size() == aOptionIds.size());

    StringVector::const_iterator
        itNext          = aCommandLine.begin();
        ++itNext;       // Move 1 forward from program name.
    StringVector::const_iterator
        itEnd           = aCommandLine.end();
    StringCIteratorList::const_iterator
        itOptPtsEnd     = aOptionPoints.end();

    OptionIdList::const_iterator
        itOptIds = aOptionIds.begin();
    for ( StringCIteratorList::const_iterator itOptPts = aOptionPoints.begin();
          itOptPts != itOptPtsEnd AND bIsOk;
          ++itOptPts, ++itOptIds )
    {
        // May be, there are arguments which do not belong to the last option:
        // itNext != *is
        Handle_FreeArguments(itNext, *itOptPts);

        itNext = do_HandleOption( *itOptIds,
                                  *itOptPts + 1,
                                  itOptPts+1 == itOptPtsEnd ? itEnd : *(itOptPts+1) );
        csv_assert(itNext <= itEnd);
    }   // end for (is)
    Handle_FreeArguments(itNext, itEnd);

    return bIsOk;
}

CommandLine::CommandLine()
    :   aOptions(),
        aCommandLine(),
        bIsOk(false)
{
}

void
CommandLine::Add_Option( intt                i_id,
                         String              i_text )
{
    aOptions.push_back(OptionDescription( i_id,
                                          i_text ));
}

void
CommandLine::Get_Arguments( int    argc,
                            char * argv[] )
{
    aCommandLine.erase(aCommandLine.begin(),aCommandLine.end());
    aCommandLine.reserve(argc);

    char ** pArgEnd = argv + argc;
    for ( char ** pArg = &argv[0];
          pArg != pArgEnd;
          ++pArg )
    {
        Store_Argument(*pArg);
    }   // end for
    Find_OptionPoints();
    bIsOk = true;
}

intt
CommandLine::Find_Option( const String & i_text ) const
{
    if (i_text.empty())
        return C_nNoOption;

    FindOptionByText aSearch(i_text);
    OptionList::const_iterator
        itFound = std::find_if( aOptions.begin(),
                                aOptions.end(),
                                aSearch );
    if (itFound != aOptions.end())
    {
        return (*itFound).nId;
    }
    return C_nNoOption;
}

bool
CommandLine::Store_Argument( const String & i_arg )
{
    if ( NOT IsIncludeOption(i_arg) )
    {
        aCommandLine.push_back(i_arg);
        return true;
    }

    return Try2Include_Options(i_arg);
}

void
CommandLine::Find_OptionPoints()
{
    StringVector::const_iterator    itEnd   = aCommandLine.end();
    for ( StringVector::const_iterator it = aCommandLine.begin() + 1;
          it != itEnd;
          ++it )
    {
        intt    nOption = Find_Option(*it);
        if (nOption != C_nNoOption)
        {
            aOptionPoints.push_back(it);
            aOptionIds.push_back(nOption);
        }
    }   // end for (i)
}

void
CommandLine::Handle_FreeArguments( StringVector::const_iterator i_begin,
                                   StringVector::const_iterator i_end )
{
    for ( StringVector::const_iterator it = i_begin;
          it != i_end AND bIsOk;
          ++it )
    {
        do_HandleFreeArgument(*it);
    }
}

bool
CommandLine::Try2Include_Options(const String & i_includeOption)
{
    static StringVector
        aIncludedOptionFiles_;

    const String
        aOptionFile(IncludeFile_fromIncludeOption(i_includeOption));

    // Avoid recursion deadlock 1
    if ( std::find( aIncludedOptionFiles_.begin(),
                    aIncludedOptionFiles_.end(),
                    aOptionFile )
         != aIncludedOptionFiles_.end() )
    {
        Cerr() << "\nError: Self inclusion of option file "
               << aOptionFile
               << ".\n"
               << Endl();
        return false;
    }

    // Avoid recursion deadlock 2
    aIncludedOptionFiles_.push_back(aOptionFile);

    bool ok = Include_Options(aOptionFile);

    // Avoid recursion deadlock 3
    aIncludedOptionFiles_.pop_back();

    return ok;
}

bool
CommandLine::Include_Options( const String & i_optionsFile )
{
    StreamStr
        aIncludedText(500);
    bool ok = Load_Options(aIncludedText, i_optionsFile);
    if (NOT ok)
        return false;

    StringVector
        aIncludedOptions;
    Split(aIncludedOptions, aIncludedText.c_str());

    StringVector::const_iterator itEnd = aIncludedOptions.end();
    for ( StringVector::const_iterator it = aIncludedOptions.begin();
          it != itEnd;
          ++it )
    {
        Store_Argument(*it);
    }   // end for

    return true;
}

bool
CommandLine::Load_Options( StreamStr &      o_text,
                           const String &   i_optionsFile )
{
    if (i_optionsFile.empty())
        return false;

    File
        aOptionsFile(i_optionsFile, CFM_READ);
    OpenCloseGuard
        aOFGuard(aOptionsFile);
    if (NOT aOFGuard)
    {
        Cerr() << "\nError: Options file "
               << i_optionsFile
               << " not found.\n"
               << Endl();
        return false;
    }

    StreamStr
        aLoad(aOptionsFile);
    o_text.swap(aLoad);
    return true;
}




/******************         OptionDescription       ***********************/


CommandLine::
OptionDescription::OptionDescription( intt          i_id,
                                      String        i_text )
    :   nId(i_id),
        sText(i_text)
{
}




}   // namespace csv
