// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// ---
// Author:   Frank H. Jernigan
//

#include "config.h"
#include <assert.h>
#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
#include <pthread.h>
#endif
#include <string>
#include <google/ctemplate/hash_map.h>
#include <google/template_from_string.h>
#include <google/template.h>

_START_GOOGLE_NAMESPACE_

#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
# define LOCK(m) pthread_mutex_lock(m)
# define UNLOCK(m) pthread_mutex_unlock(m)
  // This is used to protect g_template_from_string_cache, below
  static pthread_mutex_t g_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
# define LOCK(m)
# define UNLOCK(m)
#endif

using std::string;
using std::pair;
using HASH_NAMESPACE::hash_map;


// TemplateFromString Constructor
// Calls its parent with an empty string for filename so the parent's
// constructor will not try to "load" the template from a file. Instead,
// the template text is taken from the second parameter. After that, the
// object is identical to a Template object, except that it cannot be
// "reloaded."
TemplateFromString::TemplateFromString(const string& template_name,
                                       const string& template_text,
                                       Strip strip)
    : Template("", strip) {
  filename_ = template_name;    // for cache and reporting purposes only

  // We know that InsertFile never writes more output than it gets input.
  // While we allocate buffer here, BuildTree takes ownership and deletes it.
  char* buffer = new char[template_text.size()];
  const int buflen = InsertFile(template_text.data(), template_text.size(),
                                buffer);
  if ( BuildTree(buffer, buffer + buflen) ) {
    assert(state() == TS_READY);
  } else {
    assert(state() != TS_READY);
  }
}

// This is the only function that references the cache, so we can put all
// the cache stuff locally here.

class TemplateCacheHash {
 public:
  HASH_NAMESPACE::hash<const char *> string_hash_;
  TemplateCacheHash() : string_hash_() {}
  bool operator()(const pair<string, Strip>& p) const {
    // Using + here is silly, but should work ok in practice
    return string_hash_(p.first.c_str()) + static_cast<int>(p.second);
  }
};

// The template cache.  Note that we don't define a ClearCache() in this
// class, so there's no way to delete the entries from this cache!
typedef hash_map<pair<string, Strip>, TemplateFromString*, TemplateCacheHash>
  TemplateFromStringCache;

static TemplateFromStringCache *g_template_from_string_cache = NULL;


// TemplateFromString::GetTemplate
// Makes sure the template cache has been created and then tries to
// retrieve a TemplateFromString object from it via the template_name.
TemplateFromString *TemplateFromString::GetTemplate(const string& template_name,
                                                    const string& template_text,
                                                    Strip strip) {
  // Only perform this method when you have the lock so multiple threads
  // don't conflict over inserting and retrieving into the cache
  LOCK(&g_cache_mutex);
  if (g_template_from_string_cache == NULL) {
    g_template_from_string_cache = new TemplateFromStringCache;
  }

  // If the object isn't really a TemplateFromString, this will be a cache miss
  TemplateFromString *tpl =
    (*g_template_from_string_cache)[pair<string, Strip>(template_name, strip)];

  // If we didn't find one, then create one and store it in the cache
  if (!tpl) {
    tpl = new TemplateFromString(template_name, template_text, strip);
    (*g_template_from_string_cache)[pair<string, Strip>(template_name, strip)] =
      tpl;
  }
  UNLOCK(&g_cache_mutex);  // done messing with the cache

  // state_ can be TS_RELOAD if ReloadAllIfChanged() touched this file.
  // That's fine; we'll just ignore the reload directive for this guy.
  if (tpl->state() == TS_RELOAD)
    tpl->set_state(TS_READY);

  // if the statis is not TS_READY, then it is TS_ERROR at this
  // point. If it is TS_ERROR, we leave the state as is,
  // but return NULL. TS_ERROR means there was a syntax error in the
  // string parsed (template_text).
  if (tpl->state() != TS_READY) {
    return NULL;
  } else {
    return tpl;
  }
}

// InvalidMethodCall
// A static function yank the developer's chain
static void InvalidMethodCall(const char* method_name) {
  assert(method_name ==
         "Programming error: can't call this method on TemplateFromString");
}

// These last three methods merely call attention to the developer's error
// Since they are private, the compiler will detect such calls everywhere
// except in modifications to the other methods in this class. It is
// therefore very unlikely these will ever be called.
Template *TemplateFromString::GetTemplate(const string& filename, Strip strip) {
  InvalidMethodCall("GetTemplate");
  return NULL;
}

bool TemplateFromString::ReloadIfChanged() {
  InvalidMethodCall("ReloadIfChanged");
  return false;
}

void TemplateFromString::Reload() {
  InvalidMethodCall("Reload");
}

_END_GOOGLE_NAMESPACE_
