// SPDX-License-Identifier: LGPL-2.1-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors: see git history
 *
 * Copyright (C) 2006 Authors
 * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
 */
/* Defines String::ucompose(fmt, arg...) for easy, i18n-friendly
 * composition of strings with Gtkmm >= 1.3.* (see www.gtkmm.org).
 * Uses Glib::ustring instead of std::string which doesn't work with
 * Gtkmm due to character encoding troubles with stringstreams.
 *
 * Version 1.0.4.
 *
 * Copyright (c) 2002, 03, 04 Ole Laursen <olau@hardworking.dk>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

//
// Basic usage is like
//
//   String::ucompose("This is a %1x%2 matrix.", rows, cols);
//
// See http://www.cs.aau.dk/~olau/compose/ or the included
// README.compose for more details.
//

#ifndef STRING_UCOMPOSE_HPP
#define STRING_UCOMPOSE_HPP

#include <glibmm/ustring.h>
#include <glibmm/convert.h>

#include <stdexcept>
#include <sstream>
#include <string>
#include <list>
#include <map>			// for multimap

namespace UStringPrivate
{
  // the actual composition class - using String::ucompose is cleaner, so we
  // hide it here
  class Composition
  {
  public:
    // initialize and prepare format string on the form "text %1 text %2 etc."
    explicit Composition(std::string fmt);

    // supply an replacement argument starting from %1
    template <typename T>
    Composition &arg(const T &obj);

    // compose and return string
    Glib::ustring str() const;

  private:
  
    //This is standard, not GCC-specific like wostringstream
    std::basic_ostringstream<wchar_t> os;
    
    int arg_no;

    // we store the output as a list - when the output string is requested, the
    // list is concatenated to a string; this way we can keep iterators into
    // the list instead of into a string where they're possibly invalidated
    // when inserting a specification string
    typedef std::list<std::string> output_list;
    output_list output;

    // the initial parse of the format string fills in the specification map
    // with positions for each of the various %?s
    typedef std::multimap<int, output_list::iterator> specification_map;
    specification_map specs;

    template <typename T>
    std::string stringify(T obj);
  };

  // helper for converting spec string numbers
  inline int char_to_int(char c)
  {
    switch (c) {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    default: return -1000;
    }
  }

  inline bool is_number(int n)
  {
    switch (n) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      return true;
    
    default:
      return false;
    }
  }

  template <typename T>
  inline std::string Composition::stringify(T obj)
  {
    os << obj;

    std::wstring str = os.str();
    
    return Glib::convert(std::string(reinterpret_cast<const char *>(str.data()),
				     str.size() * sizeof(wchar_t)),
			 "UTF-8", "WCHAR_T");
  }

  // specialisations for the common string types
  template <>
  inline std::string
  Composition::stringify<std::string>(std::string obj)
  {
    return obj;
  }
  
  template <>
  inline std::string
  Composition::stringify<Glib::ustring>(Glib::ustring obj)
  {
    return obj;
  }
  
  template <>
  inline std::string
  Composition::stringify<const char *>(const char *obj)
  {
    return obj;
  }
  
  // implementation of class Composition
  template <typename T>
  inline Composition &Composition::arg(const T &obj)
  {
    Glib::ustring rep = stringify(obj);
    
    if (!rep.empty()) {		// manipulators don't produce output
      for (specification_map::const_iterator i = specs.lower_bound(arg_no),
	     end = specs.upper_bound(arg_no); i != end; ++i) {
	output_list::iterator pos = i->second;
	++pos;
      
	output.insert(pos, rep);
      }
    
      os.str(std::wstring());
      //os.clear();
      ++arg_no;
    }
  
    return *this;
  }

  inline Composition::Composition(std::string fmt)
    : arg_no(1)
  {
#if __GNUC__ >= 3
    try {
	os.imbue(std::locale("")); // use the user's locale for the stream
    } 
    catch (std::runtime_error& e) { // fallback to classic if it failed
	os.imbue(std::locale::classic());
    }
#endif
    std::string::size_type b = 0, i = 0;
  
    // fill in output with the strings between the %1 %2 %3 etc. and
    // fill in specs with the positions
    while (i < fmt.length()) {
      if (fmt[i] == '%' && i + 1 < fmt.length()) {
	if (fmt[i + 1] == '%') { // catch %%
	  fmt.replace(i, 2, "%");
	  ++i;
	}
	else if (is_number(fmt[i + 1])) { // aha! a spec!
	  // save string
	  output.push_back(fmt.substr(b, i - b));
	
	  int n = 1;		// number of digits
	  int spec_no = 0;

	  do {
	    spec_no += char_to_int(fmt[i + n]);
	    spec_no *= 10;
	    ++n;
	  } while (i + n < fmt.length() && is_number(fmt[i + n]));

	  spec_no /= 10;
	  output_list::iterator pos = output.end();
	  --pos;		// safe since we have just inserted a string
	
	  specs.insert(specification_map::value_type(spec_no, pos));
	
	  // jump over spec string
	  i += n;
	  b = i;
	}
	else
	  ++i;
      }
      else
	++i;
    }
  
    if (i - b > 0)		// add the rest of the string
      output.push_back(fmt.substr(b, i - b));
  }

  inline Glib::ustring Composition::str() const
  {
    // assemble string
    std::string str;
  
    for (const auto & i : output)
      str += i;
  
    return str;
  }
}


namespace String 
{
  // a series of functions which accept a format string on the form "text %1
  // more %2 less %3" and a number of templated parameters and spits out the
  // composited string
  template <typename T1>
  inline Glib::ustring ucompose(const Glib::ustring &fmt, const T1 &o1)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1);
    return c.str();
  }

  template <typename T1, typename T2>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2);
    return c.str();
  }

  template <typename T1, typename T2, typename T3>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10);
    return c.str();
  }
  
  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10,
	    typename T11>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10, const T11 &o11)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10).arg(o11);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10,
	    typename T11, typename T12>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10, const T11 &o11, const T12 &o12)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10).arg(o11).arg(o12);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10,
	    typename T11, typename T12, typename T13>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10, const T11 &o11, const T12 &o12,
				const T13 &o13)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10).arg(o11).arg(o12).arg(o13);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10,
	    typename T11, typename T12, typename T13, typename T14>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10, const T11 &o11, const T12 &o12,
				const T13 &o13, const T14 &o14)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10).arg(o11).arg(o12).arg(o13).arg(o14);
    return c.str();
  }

  template <typename T1, typename T2, typename T3, typename T4, typename T5,
	    typename T6, typename T7, typename T8, typename T9, typename T10,
	    typename T11, typename T12, typename T13, typename T14,
	    typename T15>
  inline Glib::ustring ucompose(const Glib::ustring &fmt,
				const T1 &o1, const T2 &o2, const T3 &o3,
				const T4 &o4, const T5 &o5, const T6 &o6,
				const T7 &o7, const T8 &o8, const T9 &o9,
				const T10 &o10, const T11 &o11, const T12 &o12,
				const T13 &o13, const T14 &o14, const T15 &o15)
  {
    UStringPrivate::Composition c(fmt);
    c.arg(o1).arg(o2).arg(o3).arg(o4).arg(o5).arg(o6).arg(o7).arg(o8).arg(o9)
      .arg(o10).arg(o11).arg(o12).arg(o13).arg(o14).arg(o15);
    return c.str();
  }
}


#endif // STRING_UCOMPOSE_HPP
