// file      : odb/include.cxx
// copyright : Copyright (c) 2009-2015 Code Synthesis Tools CC
// license   : GNU GPL v3; see accompanying LICENSE file

#include <odb/gcc.hxx>

#include <set>
#include <map>
#include <locale>
#include <cassert>
#include <fstream>
#include <sstream>

#include <odb/common.hxx>
#include <odb/context.hxx>
#include <odb/generate.hxx>

#include <iostream>

using namespace std;
using semantics::path;

namespace
{
  struct include_directive
  {
    enum type { quote, bracket };

    type type_;
    path path_;
  };

  struct includes
  {
    typedef std::map<line_map const*, include_directive> map_type;
    bool trailing; // Included at the beginning or at the end of the main file.
    map_type map;
  };
  typedef std::map<path, includes> include_map;

  // Map of files to the lines which contain include directives
  // that we are interested in.
  //
  typedef std::map<size_t, include_directive*> include_lines;
  typedef std::map<string, include_lines> file_map;

  // Set of include directives sorted in the preference order.
  //
  struct include_comparator
  {
    bool
    operator() (include_directive const* x, include_directive const* y) const
    {
      // Prefer <> over "".
      //
      if (x->type_ != y->type_)
        return x->type_ < y->type_;

      // Otherwise, prefer longer (more qualified) paths over the
      // shorter ones.
      //
      return x->path_.string ().size () < y->path_.string ().size ();
    }
  };

  typedef
  std::multiset<include_directive const*, include_comparator>
  include_set;

  struct class_: traversal::class_, context
  {
    class_ (include_map& map)
      : typedefs_ (true), main_file_loc_ (0), map_ (map)
    {
      *this >> defines_ >> *this;
      *this >> typedefs_ >> *this;
    }

    virtual void
    traverse (type& c)
    {
      class_kind_type ck (class_kind (c));

      if (ck == class_other)
        return;

      names (c); // Check nested classes.

      // We only generate things for objects and composite value types. In
      // particular, we don't care about views since they cannot be used in
      // definitions of other views, objects, or composite values.
      //
      if (ck != class_object && ck != class_composite)
        return;

      // Not interested in classes that we are generating.
      //
      // If we have an explicit definition location, use that. Otherwise,
      // if this is a class template instantiation, then get the file
      // corresponding to the pragma, not the instantiation itself,
      // since that's where we are generation the code for this class.
      // While at it, also get the location.
      //
      using semantics::path;

      path f;
      location_t l;

      if (c.count ("definition"))
      {
        l = c.get<location_t> ("definition");
        f = path (LOCATION_FILE (l));
      }
      else if (c.is_a<semantics::class_instantiation> ())
      {
        l = c.get<location_t> ("location");
        f = path (LOCATION_FILE (l));
      }
      else
      {
        f = c.file ();
        tree decl (TYPE_NAME (c.tree_node ()));
        l = real_source_location (decl);

        // Any include directives that follow are trailing (specified at
        // the end of the main file). Note that we ignore views in this
        // test so if a file defines only views, then all includes will
        // be treated as leading. This is ok since views cannot have
        // circular dependencies. We also ignore overridden locations for
        // the purpose of this test since they are not really in the file
        // being compiled. We assume that any includes that come after
        // such classes are still leading.
        //
        if (f == unit.file ())
        {
          if (main_file_loc_ == 0)
            main_file_loc_ = l;
          return;
        }
      }

      // This is a persistent object or composite value type declared in
      // another header file. Include its -odb header.
      //
      if (l > BUILTINS_LOCATION)
      {
        line_map const* lm (linemap_lookup (line_table, l));

        if (lm != 0 && !MAIN_FILE_P (lm))
        {
          lm = INCLUDED_FROM (line_table, lm);

          f.complete ();
          f.normalize ();

          if (map_.find (f) == map_.end ())
          {
            includes& i (map_[f]);
            i.trailing = (main_file_loc_ != 0 && l > main_file_loc_);
            i.map[lm] = include_directive ();
          }
        }
      }
    }

  private:
    traversal::defines defines_;
    typedefs typedefs_;

    location_t main_file_loc_;
    include_map& map_;
  };

  class include_parser
  {
  public:
    include_parser (options const& options)
        : loc_ ("C"), options_ (options)
    {
    }

    void
    parse_file (string const& file, include_lines& lines)
    {
      string f (file);
      size_t n (f.size ());
      database db (options_.database ()[0]);

      // Check if we have a synthesized prologue/epilogue fragment.
      //
      if (n != 0 && f[0] == '<' && f[n - 1] == '>')
      {
        size_t p (f.rfind ('-'));

        if (p != string::npos)
        {
          string name (f, 1, p - 1);

          if (name == "odb-prologue" || name == "odb-epilogue")
          {
            // Extract the fragment number.
            //
            {
              istringstream istr (string (f, p + 1));
              istr >> n;
            }

            n--; // Prologues/epilogues are counted from 1.

            stringstream ss;
            f.clear ();

            // We don't need the #line part.
            //
            if (name == "odb-prologue")
            {
              size_t size (options_.odb_prologue ().size ());

              if (n < size)
                ss << options_.odb_prologue ()[db][n];
              else
                f = options_.odb_prologue_file ()[db][n - size];
            }
            else
            {
              size_t size (options_.odb_epilogue ().size ());

              if (n < size)
                ss << options_.odb_epilogue ()[db][n];
              else
                f = options_.odb_epilogue_file ()[db][n - size];
            }

            if (f.empty ())
            {
              parse_stream (ss, file, lines);
              return;
            }
            // Otherwise use the code below to parse the file.
          }
        }
      }

      ifstream is (f.c_str ());

      if (!is.is_open ())
      {
        cerr << "error: unable to open '" << f << "' in read mode" << endl;
        throw operation_failed ();
      }

      parse_stream (is, f, lines);
    }

    void
    parse_stream (istream& is, string const& name, include_lines& lines)
    {
      typedef char_traits<char>::int_type int_type;

      size_t lmax (lines.rbegin ()->first);

      string line;
      bool bslash (false);
      size_t lb (1), le (1);
      bool eof (false);

      for (int_type c (is.get ()); !eof; c = is.get ())
      {
        if (is.fail ())
        {
          if (is.eof ())
          {
            // If we are still in the range, treat this as the last newline.
            //
            c = '\n';
            eof = true;
          }
          else
            break; // Some other failure -- bail out.
        }

        if (c == '\n')
        {
          le++;

          if (!bslash)
          {
            //cerr << "line: " << lb << "-" << (le - 1) << " " << line << endl;

            // See if we are interested in this range of physical lines.
            //
            include_lines::iterator li (lines.lower_bound (lb));
            include_lines::iterator ui (lines.upper_bound (le - 1));

            // We should have at most one entry per logical line.
            //
            for (; li != ui; ++li)
            {
              if (li->first >= lb && li->first <= (le - 1))
              {
                if (!parse_line (line, *li->second))
                {
                  cerr << name << ":" << lb << ":1: error: "
                       << "unable to parse #include directive" << endl;
                  throw operation_failed ();
                }
              }
            }

            if (le > lmax)
              break;

            lb = le;
            line.clear ();
          }

          bslash = false;
          continue;
        }

        if (bslash)
        {
          line += '\\';
          bslash = false;
        }

        if (c == '\\')
          bslash = true;
        else
        {
          line += char (c);
        }
      }

      if (is.bad () || (is.fail () && !is.eof ()))
      {
        cerr << "error: input error while reading '" << name << "'" << endl;
        throw operation_failed ();
      }
    }

  private:
    bool
    parse_line (string const& l, include_directive& inc)
    {
      enum state
      {
        start_hash,
        start_keyword,
        parse_keyword,
        start_path,
        parse_path,
        parse_done
      };

      bool com (false);  // In C-style comment.
      string lex;
      char path_end ('\0');
      state s (start_hash);

      for (size_t i (0), n (l.size ()); i < n; ++i)
      {
        char c (l[i]);

        if (com)
        {
          if (c == '*' && (i + 1) < n && l[i + 1] == '/')
          {
            ++i;
            com = false;
            c = ' '; // Replace a comment with a single space.
          }
          else
            continue;
        }

        // We only ignore spaces in start states.
        //
        if (is_space (c))
        {
          switch (s)
          {
          case start_hash:
          case start_keyword:
          case start_path:
            {
              continue;
            }
          default:
            {
              break;
            }
          }
        }

        // C comment can be anywhere except in the path.
        //
        if (s != parse_path && c == '/' && (i + 1) < n && l[i + 1] == '*')
        {
          ++i;
          com = true;
          continue;
        }

        switch (s)
        {
        case start_hash:
          {
            if (c != '#')
              return false;

            s = start_keyword;
            break;
          }
        case start_keyword:
          {
            lex.clear ();
            s = parse_keyword;
            // Fall through.
          }
        case parse_keyword:
          {
            if (is_alpha (c))
            {
              lex += c;
              break;
            }

            if (lex != "include")
              return false;

            s = start_path;
            --i; // Re-parse the same character again.
            break;
          }
        case start_path:
          {
            if (c == '"')
            {
              path_end = '"';
              inc.type_ = include_directive::quote;
            }
            else if (c == '<')
            {
              path_end = '>';
              inc.type_ = include_directive::bracket;
            }
            else
              return false;

            lex.clear ();
            s = parse_path;
            break;
          }
        case parse_path:
          {
            if (c != path_end)
              lex += c;
            else
              s = parse_done;

            break;
          }
        default:
          {
            assert (false);
            break;
          }
        }

        if (s == parse_done)
          break;
      }

      if (s != parse_done)
        return false;

      inc.path_ = path (lex);
      return true;
    }

  private:
    bool
    is_alpha (char c) const
    {
      return isalpha (c, loc_);
    }

    bool
    is_space (char c) const
    {
      return isspace (c, loc_);
    }

  private:
    std::locale loc_;
    options const& options_;
  };

  bool
  generate_impl (bool header)
  {
    bool r (false);

    // We do the same include directive collection and processing
    // twice, once for the header file and once for the source file.
    // If that proves to be too slow, we will need to do it only once
    // and cache the result.
    //
    context ctx;
    include_map imap;

    // Collect all the files that we need to include.
    //
    {
      traversal::unit unit;
      traversal::defines unit_defines;
      typedefs unit_typedefs (true);
      traversal::namespace_ ns;
      class_ c (imap);

      unit >> unit_defines >> ns;
      unit_defines >> c;
      unit >> unit_typedefs >> c;

      traversal::defines ns_defines;
      typedefs ns_typedefs (true);

      ns >> ns_defines >> ns;
      ns_defines >> c;
      ns >> ns_typedefs >> c;

      unit.dispatch (ctx.unit);
    }

    // Add all the known include locations for each file in the map.
    //
#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6
    size_t used (line_table->used);
    line_map const* maps (line_table->maps);
#else
    size_t used (line_table->info_ordinary.used);
    line_map const* maps (line_table->info_ordinary.maps);
#endif

    for (size_t i (0); i < used; ++i)
    {
      line_map const* m (maps + i);

      if (MAIN_FILE_P (m) || m->reason != LC_ENTER)
        continue;

      line_map const* ifm (INCLUDED_FROM (line_table, m));

#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6
      path f (m->to_file);
#else
      path f (ORDINARY_MAP_FILE_NAME (m));
#endif

      f.complete ();
      f.normalize ();

      include_map::iterator it (imap.find (f));

      if (it != imap.end ())
        it->second.map[ifm] = include_directive ();
    }

    //
    //
    file_map fmap;

    for (include_map::iterator i (imap.begin ()), e (imap.end ()); i != e; ++i)
    {
      /*
      cerr << endl
           << i->first << " included from" << endl;

      for (includes::iterator j (i->second.begin ());
           j != i->second.end (); ++j)
      {
        line_map const* lm (j->first);
        cerr << '\t' << lm->to_file << ":" << LAST_SOURCE_LINE (lm) << endl;
      }
      */

      // First see if there is an include from the main file. If so, then
      // it is preferred over all others. Use the first one if there are
      // several.
      //
      line_map const* main_lm (0);
      include_directive* main_inc (0);

      for (includes::map_type::iterator j (i->second.map.begin ());
           j != i->second.map.end (); ++j)
      {
        line_map const* lm (j->first);

        if (MAIN_FILE_P (lm))
        {
          if (main_lm == 0 ||
              LAST_SOURCE_LINE (main_lm) > LAST_SOURCE_LINE (lm))
          {
            main_lm = lm;
            main_inc = &j->second;
          }
        }
      }

      if (main_lm != 0)
      {
#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6
        string f (main_lm->to_file);
#else
        string f (ORDINARY_MAP_FILE_NAME (main_lm));
#endif
        size_t n (f.size ());

        // Check if this is a synthesized fragment.
        //
        if (!(n != 0 && f[0] == '<' && f[n - 1] == '>'))
        {
          path p (f);
          p.complete ();
          p.normalize ();
          f = p.string ();
        }

        fmap[f][LAST_SOURCE_LINE (main_lm)] = main_inc;
        continue;
      }

      // Otherwise, add all the entries.
      //
      for (includes::map_type::iterator j (i->second.map.begin ());
           j != i->second.map.end (); ++j)
      {
        line_map const* lm (j->first);

#if BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR <= 6
        string f (lm->to_file);
#else
        string f (ORDINARY_MAP_FILE_NAME (lm));
#endif
        size_t n (f.size ());

        // Check if this is a synthesized fragment.
        //
        if (!(n != 0 && f[0] == '<' && f[n - 1] == '>'))
        {
          path p (f);
          p.complete ();
          p.normalize ();
          f = p.string ();
        }

        fmap[f][LAST_SOURCE_LINE (lm)] = &j->second;
      }
    }

    // Parse the collected include directives.
    //
    include_parser ip (ctx.options);

    for (file_map::iterator i (fmap.begin ()), e (fmap.end ()); i != e; ++i)
    {
      ip.parse_file (i->first, i->second);
    }

    // Finally, output the include directives.
    //
    for (include_map::const_iterator i (imap.begin ()), e (imap.end ());
         i != e; ++i)
    {
      includes const& is (i->second);

      // In header we generate only leading includes. In source -- only
      // trailing.
      //
      if (header == is.trailing)
        continue;

      include_directive const* inc (0);

      if (is.map.size () == 1)
      {
        inc = &is.map.begin ()->second;
      }
      else
      {
        include_set set;

        for (includes::map_type::const_iterator j (i->second.map.begin ());
             j != i->second.map.end (); ++j)
        {
          if (!j->second.path_.empty ())
            set.insert (&j->second);
        }

        assert (set.size () > 0);
        inc = *set.rbegin ();
      }

      path f (inc->path_.base ());
      f += ctx.options.odb_file_suffix ()[ctx.options.database ()[0]];
      f += ctx.options.hxx_suffix ();

      char o (inc->type_ == include_directive::quote ? '"' : '<');
      ctx.os << "#include " << ctx.process_include_path (
        f.string (), false, o) << endl;
      r = true;
    }

    return r;
  }
}

namespace include
{
  bool
  generate (bool header) {return generate_impl (header);}
}
