// Copyright (C) 2003 Tom Tromey <tromey@redhat.com>
//
// This program is made available under the GNU GPL version 2.0 or
// greater. See the accompanying file COPYING for details.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include <cstring> // for strerror
#include <cstdio>  // for rename
#include <cerrno>  // for errno

using std::back_inserter;
using std::cerr;
using std::copy;
using std::filebuf;
using std::ios;
using std::istreambuf_iterator;
using std::ostream;
using std::ostringstream;
using std::ofstream;
using std::rename;
using std::remove;
using std::strerror;
using std::string;

struct ioerror
{
  int errcode;
  string fname;
  string operation;

  ioerror(char const * f, char const * op)
    : errcode(errno), fname(f), operation(op)
  {}
  ioerror(string const & f, char const * op)
    : errcode(errno), fname(f), operation(op)
  {}
};

ostream &
operator<<(ostream & s, ioerror const & e)
{
  s << e.fname << ": "
    << e.operation << " failed: "
    << strerror(errno) << '\n';
  return s;
}

static void
generate_code(string & result,
              char const * fname,
              char const * arrayname,
              bool static_array,
              bool strip_trailing)
{
  string text;

  {
    filebuf fin;
    if (!fin.open(fname, ios::in))
      throw ioerror(fname, "open");
    copy(istreambuf_iterator<char>(&fin), istreambuf_iterator<char>(),
         back_inserter(text));
  }
  if (strip_trailing)
    {
      int last = text.find_last_not_of(" \t\n");
      text.erase(last + 1);
    }

  ostringstream os;

  os << "// DO NOT EDIT\n"
     << "// this file is automatically generated from " << fname << ",\n"
     << "// any changes you make will be destroyed when it is regenerated\n"
     << "\n\n";

  if (static_array)
    os << "static ";
  else
    // some versions of g++ object to constants marked 'extern' and defined
    // at the same time (i.e. constants declared with both 'extern' and an
    // initializer).  to shut them up, first declare the constant 'extern',
    // then define it without 'extern'.
    os << "extern char const " << arrayname << "_constant[];\n";

  os << "char const " << arrayname << "_constant["
     << (text.size() + 1) << "] = {\n";

  for (unsigned int i = 0; i < text.size(); ++i)
    {
      if (i == 0) os << '\t';
      else if (i % 14 == 0) os << "\n\t";
      os << static_cast<int>(text[i]) << ", ";
    }
  os << "0\n};\n";

  result = os.str();
}

static bool
compare_contents(char const *fname, string const & text)
{
  filebuf fin;
  if (!fin.open(fname, ios::in))
    {
      if (errno != ENOENT)
        throw ioerror(fname, "open");
      return false;
    }

  istreambuf_iterator<char> fp(&fin), fend;
  string::const_iterator tp(text.begin()), tend(text.end());

  // cannot use std::equal() because it does not check that the
  // sequences are the same length
  while (fp != fend && tp != tend)
    {
      if (*fp != *tp)
        return false;
      fp++;
      tp++;
    }

  return fp == fend && tp == tend;
}

static void
atomic_update_if_changed(char const *ofname, string const & text)
{
  if (compare_contents(ofname, text))
    return;

  string tfname(ofname);
  tfname += "T";

  {
    ofstream fout(tfname.c_str());
    if (!fout)
      throw ioerror(tfname, "open");

    fout.write(text.data(), text.size());
    if (!fout.flush())
      throw ioerror(tfname, "write");
  }

  if (rename(tfname.c_str(), ofname))
    {
      remove(ofname);
      if (rename(tfname.c_str(), ofname))
        throw ioerror(ofname, "rename");
    }
}

int
main(int argc, char **argv)
{
  bool do_strip_trailing = false;
  bool do_static = false;

  int i = 1;
  if (i < argc && string(argv[i]) == "--strip-trailing")
    {
      do_strip_trailing = true;
      i++;
    }
  if (i < argc && string(argv[i]) == "--static")
    {
      do_static = true;
      i++;
    }
  if (argc - i != 3)
    {
      cerr << "usage: " << argv[0]
           << (" [--strip-trailing] [--static]"
               " <arrayname> <input> <output>\n");
      return 1;
    }

  char const * arr = argv[i++];
  char const * ifname = argv[i++];
  char const * ofname = argv[i];

  try
    {
      string text;
      generate_code(text, ifname, arr, do_static, do_strip_trailing);
      atomic_update_if_changed(ofname, text);
    }
  catch (ioerror const & e)
    {
      cerr << e;
      return 1;
    }
  return 0;
}

// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
