// (C) Copyright Jonathan Turkanis 2003.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

#ifndef BOOST_IOSTREAMS_DICTIONARY_FILTER_HPP_INCLUDED
#define BOOST_IOSTREAMS_DICTIONARY_FILTER_HPP_INCLUDED

#include <algorithm>         // swap.
#include <cassert>
#include <cstdio>            // EOF.
#include <iostream>          // cin, cout.
#include <cctype>
#include <map>
#include <boost/config.hpp>  // BOOST_NO_STDC_NAMESPACE.
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/filter/stdio.hpp>
#include <boost/iostreams/operations.hpp>

#ifdef BOOST_NO_STDC_NAMESPACE
namespace std {
    using ::isalpha;
    using ::isupper;
    using ::toupper;
    using ::tolower;
}
#endif

namespace boost { namespace iostreams { namespace example {

class dictionary {
public:
    void add(std::string key, const std::string& value);
    void replace(std::string& key);
private:
    typedef std::map<std::string, std::string> map_type;
    void tolower(std::string& str);
    map_type map_;
};

class dictionary_stdio_filter : public stdio_filter {
public:
    dictionary_stdio_filter(dictionary& d) : dictionary_(d) { }
private:
    void do_filter()
    {
        using namespace std;
        while (true) {
            int c = std::cin.get();
            if (c == EOF || !std::isalpha((unsigned char) c)) {
                dictionary_.replace(current_word_);
                cout.write( current_word_.data(),
                            static_cast<streamsize>(current_word_.size()) );
                current_word_.erase();
                if (c == EOF)
                    break;
                cout.put(c);
            } else {
                current_word_ += c;
            }
        }
    }
    dictionary&  dictionary_;
    std::string  current_word_;
};

class dictionary_input_filter : public input_filter {
public:
    dictionary_input_filter(dictionary& d)
        : dictionary_(d), off_(std::string::npos), eof_(false)
        { }

    template<typename Source>
    int get(Source& src)
        {
            // Handle unfinished business.
            if (off_ != std::string::npos && off_ < current_word_.size())
                return current_word_[off_++];
            if (off_ == current_word_.size()) {
                current_word_.erase();
                off_ = std::string::npos;
            }
            if (eof_)
                return EOF;

            // Compute curent word.
            while (true) {
                int c;
                if ((c = iostreams::get(src)) == WOULD_BLOCK)
                    return WOULD_BLOCK;

                if (c == EOF || !std::isalpha((unsigned char) c)) {
                    dictionary_.replace(current_word_);
                    off_ = 0;
                    if (c == EOF)
                        eof_ = true;
                    else
                        current_word_ += c;
                    break;
                } else {
                    current_word_ += c;
                }
            }

            return this->get(src); // Note: current_word_ is not empty.
        }

    template<typename Source>
    void close(Source&)
    {
        current_word_.erase();
        off_ = std::string::npos;
        eof_ = false;
    }
private:
    dictionary&             dictionary_;
    std::string             current_word_;
    std::string::size_type  off_;
    bool                    eof_;
};

class dictionary_output_filter : public output_filter {
public:
    typedef std::map<std::string, std::string> map_type;
    dictionary_output_filter(dictionary& d)
        : dictionary_(d), off_(std::string::npos)
        { }

    template<typename Sink>
    bool put(Sink& dest, int c)
    {
        if (off_ != std::string::npos && !write_current_word(dest))
            return false;
        if (!std::isalpha((unsigned char) c)) {
            dictionary_.replace(current_word_);
            off_ = 0;
        }

        current_word_ += c;
        return true;
    }

    template<typename Sink>
    void close(Sink& dest)
    {
        // Reset current_word_ and off_, saving old values.
        std::string             current_word;
        std::string::size_type  off = 0;
        current_word.swap(current_word_);
        std::swap(off, off_);

        // Write remaining characters to dest.
        if (off == std::string::npos) {
            dictionary_.replace(current_word);
            off = 0;
        }
        if (!current_word.empty())
            iostreams::write(
                dest,
                current_word.data() + off,
                static_cast<std::streamsize>(current_word.size() - off)
            );
    }
private:
    template<typename Sink>
    bool write_current_word(Sink& dest)
    {
        using namespace std;
        streamsize amt = static_cast<streamsize>(current_word_.size() - off_);
        streamsize result =
            iostreams::write(dest, current_word_.data() + off_, amt);
        if (result == amt) {
            current_word_.erase();
            off_ = string::npos;
            return true;
        } else {
            off_ += result;
            return false;
        }
    }

    dictionary&             dictionary_;
    std::string             current_word_;
    std::string::size_type  off_;
    bool                    initialized_;
};

//------------------Implementation of dictionary------------------------------//

inline void dictionary::add(std::string key, const std::string& value)
{
    tolower(key);
    map_[key] = value;
}

inline void dictionary::replace(std::string& key)
{
    using namespace std;
    string copy(key);
    tolower(copy);
    map_type::iterator it = map_.find(key);
    if (it == map_.end())
        return;
    string& value = it->second;
    if (!value.empty() && !key.empty() && std::isupper((unsigned char) key[0]))
        value[0] = std::toupper((unsigned char) value[0]);
    key = value;
    return;
}

inline void dictionary::tolower(std::string& str)
{
    for (std::string::size_type z = 0, len = str.size(); z < len; ++z)
        str[z] = std::tolower((unsigned char) str[z]);
}

} } }       // End namespaces example, iostreams, boost.

#endif      // #ifndef BOOST_IOSTREAMS_DICTIONARY_FILTER_HPP_INCLUDED
