#ifndef LIGO_CHANNEL_COMPLETION_HH
#define LIGO_CHANNEL_COMPLETION_HH

// Copyright 2019 California Institute of Technology.

// You should have received a copy of the licensing terms for this
// software included in the file “LICENSE” located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE.txt

#include <algorithm>
#include <iterator>
#include <fstream>
#include <ostream>
#include <string>
#include <vector>

#include <cstring>

#include <boost/iterator/transform_iterator.hpp>

namespace completion
{

    typedef std::vector< std::string > string_list;

    struct channel
    {
        std::string name;
        std::string extra;

        channel( ) = default;
        channel( std::string name, std::string extra = "" )
            : name( std::move( name ) ), extra( std::move( extra ) )
        {
        }
    };

    std::ostream&
    operator<<( std::ostream& os, const channel& ch )
    {
        os << ch.name;
        if ( !ch.extra.empty( ) )
        {
            os << " " << ch.extra;
        }
        return os;
    }

    struct Database
    {
        std::vector< channel > channels;
    };

    namespace detail
    {
        const std::string&
        project_name( const channel& entry )
        {
            return entry.name;
        }
    }

    inline bool
    simple_comparison( const channel& a, const channel& b )
    {
        return a.name < b.name;
    }

    class key_comparison
    {
    public:
        explicit key_comparison( std::string::size_type key_len )
            : key_len_( key_len )
        {
        }
        bool
        operator( )( const std::string& str1, const std::string& str2 ) const
        {
            std::string::size_type n =
                std::min( key_len_, std::max( str1.size( ), str2.size( ) ) );
            int result = std::strncmp( str1.c_str( ), str2.c_str( ), n );
            return result < 0;
        }

    private:
        std::string::size_type key_len_;
    };

    inline Database
    load_database( const std::string& path )
    {
        Database      db;
        std::ifstream input( path.c_str( ) );
        std::string   line;
        while ( std::getline( input, line ) )
        {
            auto        space = line.find( ' ' );
            std::string extra;
            if ( space < std::string::npos )
            {
                extra = line.substr( space + 1 );
                line.resize( space );
            }
            db.channels.emplace_back( line, extra );
        }
        return db;
    }

    template < typename It >
    string_list
    search( It begin_it_, It end_it_, const std::string& key )
    {
        string_list results;

        auto begin_it =
            boost::make_transform_iterator( begin_it_, detail::project_name );
        auto end_it =
            boost::make_transform_iterator( end_it_, detail::project_name );

        auto           key_size = key.size( );
        key_comparison comp( key_size );

        auto lower = std::lower_bound( begin_it, end_it, key, comp );
        auto upper = std::upper_bound( lower, end_it, key, comp );
        std::for_each(
            lower, upper, [key_size, &results]( const std::string& entry ) {
                static const std::string delimiters( "-_" );
                auto pos = entry.find_first_of( delimiters, key_size );
                if ( pos == key_size )
                {
                    pos = entry.find_first_of( delimiters, key_size + 1 );
                }
                if ( pos != std::string::npos )
                {
                    if ( results.empty( ) ||
                         std::strncmp( results.back( ).c_str( ),
                                       entry.c_str( ),
                                       pos ) != 0 )
                    {
                        results.emplace_back( entry.c_str( ), pos );
                    }
                    if ( results.size( ) <= 1 && pos != std::string::npos )
                    {
                        pos = entry.find_first_of( delimiters, pos + 1 );
                        pos =
                            ( pos == std::string::npos ? entry.size( ) : pos );
                    }
                }
                else
                {
                    results.emplace_back( entry );
                }
            } );
        return results;
    }

    inline void
    sort( Database& db )
    {
        std::sort(
            db.channels.begin( ), db.channels.end( ), simple_comparison );
    }
}

#endif // LIGO_CHANNEL_COMPLETION_HH
