/* Copyright (c) 2016-2019 The Khronos Group Inc.
 * Copyright (c) 2016-2019 Valve Corporation
 * Copyright (c) 2016-2019 LunarG, Inc.
 * Copyright (c) 2016-2019 Google Inc.
 *
 * Licensed 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.
 */
#ifndef PARAMETER_NAME_H
#define PARAMETER_NAME_H

#include <cassert>
#include <sstream>
#include <string>
#include <vector>

/**
 * Parameter name string supporting deferred formatting for array subscripts.
 *
 * Custom parameter name class with support for deferred formatting of names containing array subscripts.  The class stores
 * a format string and a pointer to an array of index values, and performs string formatting when an accessor function is called to
 * retrieve the name string.  This class was primarily designed to be used with validation functions that receive a parameter name
 * string and value as arguments, and print an error message that includes the parameter name when the value fails a validation
 * test.  Using standard strings with these validation functions requires that parameter names containing array subscripts be
 * formatted before each validation function is called, performing the string formatting even when the value passes validation
 * and the string is not used:
 *         sprintf(name, "pCreateInfo[%d].sType", i);
 *         validate_stype(name, pCreateInfo[i].sType);
 *
 * With the ParameterName class, a format string and a pointer to an array of format values are stored by the ParameterName object
 * that is provided to the validation function.  String formatting is then performed only when the validation function retrieves the
 * name string from the ParameterName object:
 *         validate_stype(ParameterName("pCreateInfo[%i].sType", IndexVector{ i }), pCreateInfo[i].sType);
 *
 * Since the IndexVector is not copied into the object, the lifetime of the ParameterName should not outlast the lifetime of
 * the IndexVector, but that's fine given how it is used in parameter validation.
 */
class ParameterName {
  public:
    /// Container for index values to be used with parameter name string formatting.
    typedef std::initializer_list<size_t> IndexVector;

    /// Format specifier for the parameter name string, to be replaced by an index value.  The parameter name string must contain
    /// one format specifier for each index value specified.
    const char *const IndexFormatSpecifier = "%i";

  public:
    /**
     * Construct a ParameterName object from a string literal, without formatting.
     *
     * @param source Paramater name string without format specifiers.
     *
     * @pre The source string must not contain the %i format specifier.
     */
    ParameterName(const char *source) : source_(source), args_(nullptr), num_indices_(0) { assert(IsValid()); }

    /**
     * Construct a ParameterName object from a string literal, with formatting.
     *
     * @param source Paramater name string with format specifiers.
     * @param args Array index values to be used for formatting.
     *
     * @pre The number of %i format specifiers contained by the source string must match the number of elements contained
     *      by the index vector.
     */
    ParameterName(const char *source, const IndexVector &args)
        : source_(source), args_(args.size() ? args.begin() : (const size_t *)nullptr), num_indices_(args.size()) {
        assert(IsValid());
    }

    /// Retrive the formatted name string.
    std::string get_name() const { return (num_indices_ == 0) ? std::string(source_) : Format(); }

  private:
    /// Replace the %i format specifiers in the source string with the values from the index vector.
    std::string Format() const {
        std::string::size_type current = 0;
        std::string::size_type last = 0;
        std::stringstream format;

        std::string source(source_);

        for (size_t i = 0; i < num_indices_; ++i) {
            auto index = args_[i];
            current = source.find(IndexFormatSpecifier, last);
            if (current == std::string::npos) {
                break;
            }
            format << source.substr(last, (current - last)) << index;
            last = current + strlen(IndexFormatSpecifier);
        }

        format << source.substr(last, std::string::npos);

        return format.str();
    }

    /// Check that the number of %i format specifiers in the source string matches the number of elements in the index vector.
    bool IsValid() {
        // Count the number of occurances of the format specifier
        uint32_t count = 0;

        std::string source(source_);

        std::string::size_type pos = source.find(IndexFormatSpecifier);

        while (pos != std::string::npos) {
            ++count;
            pos = source.find(IndexFormatSpecifier, pos + 1);
        }

        return (count == num_indices_);
    }

  private:
    const char *source_;  ///< Format string.
    const size_t *args_;  ///< Array index values for formatting.
    size_t num_indices_;  ///< Number of array index values.
};

#endif  // PARAMETER_NAME_H
