/*
 * This file is part of the API Extractor project.
 *
 * Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 * Copyright 2005 Roberto Raggi <roberto@kdevelop.org>
 *
 * Contact: PySide team <contact@pyside.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#ifndef PP_MACRO_EXPANDER_H
#define PP_MACRO_EXPANDER_H

namespace rpp
{

struct pp_frame {
    pp_macro *expanding_macro;
    std::vector<std::string> *actuals;

    pp_frame(pp_macro *__expanding_macro, std::vector<std::string> *__actuals):
            expanding_macro(__expanding_macro), actuals(__actuals) {}
};

class pp_macro_expander
{
    pp_environment &env;
    pp_frame *frame;

    pp_skip_number skip_number;
    pp_skip_identifier skip_identifier;
    pp_skip_string_literal skip_string_literal;
    pp_skip_char_literal skip_char_literal;
    pp_skip_argument skip_argument;
    pp_skip_comment_or_divop skip_comment_or_divop;
    pp_skip_blanks skip_blanks;
    pp_skip_whitespaces skip_whitespaces;

    std::string const *resolve_formal(pp_fast_string const *__name) {
        assert(__name != 0);

        if (! frame)
            return 0;

        assert(frame->expanding_macro != 0);

        std::vector<pp_fast_string const *> const formals = frame->expanding_macro->formals;
        for (std::size_t index = 0; index < formals.size(); ++index) {
            pp_fast_string const *formal = formals[index];

            if (*formal != *__name)
                continue;

            else if (frame->actuals && index < frame->actuals->size())
                return &(*frame->actuals)[index];

            else
                assert(0);  // internal error?
        }

        return 0;
    }

public: // attributes
    int lines;
    int generated_lines;

public:
    pp_macro_expander(pp_environment &__env, pp_frame *__frame = 0):
            env(__env), frame(__frame), lines(0), generated_lines(0) {}

    template <typename _InputIterator, typename _OutputIterator>
    _InputIterator operator()(_InputIterator __first, _InputIterator __last, _OutputIterator __result) {
        generated_lines = 0;
        __first = skip_blanks(__first, __last);
        lines = skip_blanks.lines;

        while (__first != __last) {
            if (*__first == '\n') {
                *__result++ = *__first;
                ++lines;

                __first = skip_blanks(++__first, __last);
                lines += skip_blanks.lines;

                if (__first != __last && *__first == '#')
                    break;
            } else if (*__first == '#') {
                __first = skip_blanks(++__first, __last);
                lines += skip_blanks.lines;

                _InputIterator end_id = skip_identifier(__first, __last);

                // ### rewrite: not safe
                char name_buffer[512], *cp = name_buffer;
                std::copy(__first, end_id, cp);
                std::size_t name_size = end_id - __first;
                name_buffer[name_size] = '\0';

                pp_fast_string fast_name(name_buffer, name_size);

                if (std::string const *actual = resolve_formal(&fast_name)) {
                    *__result++ = '\"';

                    for (std::string::const_iterator it = skip_whitespaces(actual->begin(), actual->end());
                         it != actual->end(); ++it) {
                        if (*it == '"') {
                            *__result++ = '\\';
                            *__result++ = *it;
                        }

                        else if (*it == '\n') {
                            *__result++ = '"';
                            *__result++ = '\n';
                            *__result++ = '"';
                        }

                        else
                            *__result++ = *it;
                    }

                    *__result++ = '\"';
                    __first = end_id;
                } else
                    *__result++ = '#'; // ### warning message?
            } else if (*__first == '\"') {
                _InputIterator next_pos = skip_string_literal(__first, __last);
                lines += skip_string_literal.lines;
                std::copy(__first, next_pos, __result);
                __first = next_pos;
            } else if (*__first == '\'') {
                _InputIterator next_pos = skip_char_literal(__first, __last);
                lines += skip_char_literal.lines;
                std::copy(__first, next_pos, __result);
                __first = next_pos;
            } else if (_PP_internal::comment_p(__first, __last)) {
                __first = skip_comment_or_divop(__first, __last);
                int n = skip_comment_or_divop.lines;
                lines += n;

                while (n-- > 0)
                    *__result++ = '\n';
            } else if (pp_isspace(*__first)) {
                for (; __first != __last; ++__first) {
                    if (*__first == '\n' || !pp_isspace(*__first))
                        break;
                }

                *__result = ' ';
            } else if (pp_isdigit(*__first)) {
                _InputIterator next_pos = skip_number(__first, __last);
                lines += skip_number.lines;
                std::copy(__first, next_pos, __result);
                __first = next_pos;
            } else if (pp_isalpha(*__first) || *__first == '_') {
                _InputIterator name_begin = __first;
                _InputIterator name_end = skip_identifier(__first, __last);
                __first = name_end; // advance

                // search for the paste token
                _InputIterator next = skip_blanks(__first, __last);
                if (next != __last && *next == '#') {
                    ++next;
                    if (next != __last && *next == '#')
                        __first = skip_blanks(++next, __last);
                }

                // ### rewrite: not safe

                std::ptrdiff_t name_size;
#if defined(__SUNPRO_CC)
                std::distance(name_begin, name_end, name_size);
#else
                name_size = std::distance(name_begin, name_end);
#endif
                assert(name_size >= 0 && name_size < 512);

                char name_buffer[512], *cp = name_buffer;
                std::size_t __size = name_end - name_begin;
                std::copy(name_begin, name_end, cp);
                name_buffer[__size] = '\0';

                pp_fast_string fast_name(name_buffer, name_size);

                if (std::string const *actual = resolve_formal(&fast_name)) {
                    std::copy(actual->begin(), actual->end(), __result);
                    continue;
                }

                static bool hide_next = false; // ### remove me

                pp_macro *macro = env.resolve(name_buffer, name_size);
                if (! macro || macro->hidden || hide_next) {
                    hide_next = ! strcmp(name_buffer, "defined");

                    if (__size == 8 && name_buffer [0] == '_' && name_buffer [1] == '_') {
                        if (! strcmp(name_buffer, "__LINE__")) {
                            char buf [16];
                            char *end = buf + pp_snprintf(buf, 16, "%d", env.current_line + lines);

                            std::copy(&buf [0], end, __result);
                            continue;
                        }

                        else if (! strcmp(name_buffer, "__FILE__")) {
                            __result++ = '"';
                            std::copy(env.current_file.begin(), env.current_file.end(), __result);    // ### quote
                            __result++ = '"';
                            continue;
                        }
                    }

                    std::copy(name_begin, name_end, __result);
                    continue;
                }

                if (! macro->function_like) {
                    pp_macro *m = 0;

                    if (macro->definition) {
                        macro->hidden = true;

                        std::string __tmp;
                        __tmp.reserve(256);

                        pp_macro_expander expand_macro(env);
                        expand_macro(macro->definition->begin(), macro->definition->end(), std::back_inserter(__tmp));
                        generated_lines += expand_macro.lines;

                        if (! __tmp.empty()) {
                            std::string::iterator __begin_id = skip_whitespaces(__tmp.begin(), __tmp.end());
                            std::string::iterator __end_id = skip_identifier(__begin_id, __tmp.end());

                            if (__end_id == __tmp.end()) {
                                std::string __id;
                                __id.assign(__begin_id, __end_id);

                                std::size_t x;
#if defined(__SUNPRO_CC)
                                std::distance(__begin_id, __end_id, x);
#else
                                x = std::distance(__begin_id, __end_id);
#endif
                                m = env.resolve(__id.c_str(), x);
                            }

                            if (! m)
                                std::copy(__tmp.begin(), __tmp.end(), __result);
                        }

                        macro->hidden = false;
                    }

                    if (! m)
                        continue;

                    macro = m;
                }

                // function like macro
                _InputIterator arg_it = skip_whitespaces(__first, __last);

                if (arg_it == __last || *arg_it != '(') {
                    std::copy(name_begin, name_end, __result);
                    lines += skip_whitespaces.lines;
                    __first = arg_it;
                    continue;
                }

                std::vector<std::string> actuals;
                actuals.reserve(5);
                ++arg_it; // skip '('

                pp_macro_expander expand_actual(env, frame);

                _InputIterator arg_end = skip_argument_variadics(actuals, macro, arg_it, __last);
                if (arg_it != arg_end) {
                    std::string actual(arg_it, arg_end);
                    actuals.resize(actuals.size() + 1);
                    actuals.back().reserve(255);
                    expand_actual(actual.begin(), actual.end(), std::back_inserter(actuals.back()));
                    arg_it = arg_end;
                }

                while (arg_it != __last && *arg_end == ',') {
                    ++arg_it; // skip ','

                    arg_end = skip_argument_variadics(actuals, macro, arg_it, __last);
                    std::string actual(arg_it, arg_end);
                    actuals.resize(actuals.size() + 1);
                    actuals.back().reserve(255);
                    expand_actual(actual.begin(), actual.end(), std::back_inserter(actuals.back()));
                    arg_it = arg_end;
                }

                assert(arg_it != __last && *arg_it == ')');

                ++arg_it; // skip ')'
                __first = arg_it;

#if 0 // ### enable me
                assert((macro->variadics && macro->formals.size() >= actuals.size())
                       || macro->formals.size() == actuals.size());
#endif

                pp_frame frame(macro, &actuals);
                pp_macro_expander expand_macro(env, &frame);
                macro->hidden = true;
                expand_macro(macro->definition->begin(), macro->definition->end(), __result);
                macro->hidden = false;
                generated_lines += expand_macro.lines;
            } else
                *__result++ = *__first++;
        }

        return __first;
    }

    template <typename _InputIterator>
    _InputIterator skip_argument_variadics(std::vector<std::string> const &__actuals, pp_macro *__macro,
                                           _InputIterator __first, _InputIterator __last) {
        _InputIterator arg_end = skip_argument(__first, __last);

        while (__macro->variadics && __first != arg_end && arg_end != __last && *arg_end == ','
               && (__actuals.size() + 1) == __macro->formals.size()) {
            arg_end = skip_argument(++arg_end, __last);
        }

        return arg_end;
    }
};

} // namespace rpp

#endif // PP_MACRO_EXPANDER_H

// kate: space-indent on; indent-width 2; replace-tabs on;
