/* 
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */
#include "stdafx.h"

#include "common.h"
#include "base/string_utilities.h"
#include "base/threading.h"
#include "grts/structs.h"
#include "base/log.h"


DEFAULT_LOG_DOMAIN("bec");

#include <cstdlib>

using namespace base;

// XXX: most of the code here is not utf-8 safe, check that.
// TODO: The content here should all move the base library.
namespace bec {

  std::string fmttime(time_t t, const char *fmt)
  {
    char date[100];
  #ifdef _WIN32
    errno_t err;
  #else
    int err;
  #endif
    struct tm newtime;


    if (t == 0)
      time(&t);

  #ifdef _WIN32
    err= localtime_s(&newtime, &t);
  #else
    localtime_r(&t, &newtime);
    err= 0;
  #endif

    if (!err)
      strftime(date, sizeof(date), fmt, &newtime);
    else
      date[0]= 0;

    return date;
  }
  
  std::string replace_string(const std::string &s,
                                    const std::string &from,
                                    const std::string &to)
  {
    std::string::size_type p;
    std::string ss, res;

    ss= s;
    p= ss.find(from);
    while (p != std::string::npos)
    {
      if (p > 0)
        res.append(ss.substr(0, p)).append(to);
      else
        res.append(to);
      ss= ss.substr(p+from.size());
      p= ss.find(from);
    }
    res.append(ss);

    return res;
  }


  void replace_string_inplace(std::string &text, const std::string &from, const std::string &to)
  {
    std::string::size_type from_len= from.length();
    std::string::size_type p= text.find(from);
    while (p != std::string::npos)
    {
      text.replace(p, from_len, to);
      p= text.find(from, p);
    }
  }

  std::string replace_variable(const std::string &format, const std::string &variable, const std::string &value)
  {

    std::string result= format;
    std::string::size_type pos;
  
    for (;;)
    {
      std::string s;
      std::string::size_type end;
      
      pos= result.find(variable.substr(0, variable.size()-1));
      if (pos == std::string::npos)
        break;
      
      end= result.find('%', pos+1);
      if (end == std::string::npos) // bad format
        break;

      s= result.substr(pos+1, end-pos-1);

      std::string::size_type filter_pos= s.find("|");
      std::string filtered_value= value;
      
      if (filter_pos == std::string::npos)
      {
        if (s.length() != variable.length()-2)
          break;
      }
      else if (filter_pos != variable.length()-2)
        break;
      else
      {
        std::string filter= s.substr(filter_pos+1, s.size()-filter_pos);

        if (filter.compare("capitalize") == 0)
        {
          gunichar ch= g_utf8_get_char(value.data());
          
          ch= g_unichar_toupper(ch);
          
          gchar *rest= g_utf8_find_next_char(value.data(), value.data()+value.size());
          char utf8[10];
          utf8[g_unichar_to_utf8(ch, utf8)]= 0;
          filtered_value= std::string(utf8).append(rest);
        }
        else if (filter.compare("uncapitalize") == 0)
        {
          gunichar ch= g_utf8_get_char(value.data());

          ch= g_unichar_tolower(ch);

          gchar *rest= g_utf8_find_next_char(value.data(), value.data()+value.size());
          char utf8[10];
          utf8[g_unichar_to_utf8(ch, utf8)]= 0;
          filtered_value= std::string(utf8).append(rest);
        }
        else if (filter.compare("lower") == 0)
        {
          gchar *l= g_utf8_strdown(value.data(), value.size());
          if (l)
            filtered_value= l;
          g_free(l);
        }
        else if (filter.compare("upper") == 0)
        {
          gchar *l= g_utf8_strup(value.data(), value.size());
          if (l)
            filtered_value= l;
          g_free(l);
        }
      }
      result= result.substr(0, pos).append(filtered_value).append(result.substr(end+1));
    }
    
    return result;
  }
  
  
  std::string append_extension_if_needed(const std::string &path,
                                         const std::string &ext)
  {
    if (!has_suffix(path, ext))
      return path+ext;
    return path;
  }


  std::string make_path(const std::string &prefix, const std::string &file)
  {
    if (prefix.empty())
      return file;

    if (prefix[prefix.size()-1] == '/' || prefix[prefix.size()-1] == '\\')
      return prefix+file;
    return prefix+G_DIR_SEPARATOR+file;
  }



  static bool validate_member(const grt::ClassMember *member, const GrtObjectRef &object, bool &failed)
  {
    GrtObjectRef child;
    switch (member->type.base.type)
    {
      case grt::DictType:
        break;
      case grt::ListType:
        if (member->owned_object)
        {
          grt::BaseListRef list(grt::BaseListRef::cast_from(object->get_member(member->name)));
          for (size_t i = 0; i < list.count(); i++)
          {
            if (GrtObjectRef::can_wrap(list.get(i)))
            {
              GrtObjectRef child(GrtObjectRef::cast_from(list.get(i)));
              if (!child.is_valid())
              {
                failed = true;
                log_error("Child item %li of list %s::%s is NULL", i, GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str());
              }
              else
              {
                if (!child->owner().is_valid())
                {
                  log_error("owner of %s::%s[%li] %s is NULL (expected %s <%s>)\n", GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str(), i, child->id().c_str(),
                            object->id().c_str(), object->class_name().c_str());
                  failed = true;
                }
                else if (child->owner() != object)
                {
                  log_error("owner of %s::%s[%li] %s is wrong (expected %s <%s>, is %s <%s>)\n", GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str(), i, child->id().c_str(),
                            object->id().c_str(), object->class_name().c_str(), child->owner()->id().c_str(), child->owner()->class_name().c_str());
                  failed = true;
                }
                else
                  failed = validate_tree_structure(child) || failed;
              }
            }
            else
              log_info("Unknown object in list %s\n", member->name.c_str());
          }
        }
        else
        {
          grt::BaseListRef list(grt::BaseListRef::cast_from(object->get_member(member->name)));
          for (size_t i = 0; i < list.count(); i++)
          {
            if (GrtObjectRef::can_wrap(list.get(i)))
            {
              GrtObjectRef child(GrtObjectRef::cast_from(list.get(i)));
              if (!child.is_valid())
              {
                log_error("Child item %li of list %s::%s is NULL", i, GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str());
                failed = true;
              }
              else
              {
                if (!child->owner().is_valid())
                {
                  log_error("owner of %s::%s[%li] %s is NULL (expected %s <%s>)\n", GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str(), i, child->id().c_str(),
                            object->id().c_str(), object->class_name().c_str());
                  failed = true;
                }
                else if (child->owner() == object)
                {
                  log_error("owner of %s::%s[%li] is wrong (not supposed to be %s)\n", GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str(), i,
                            child->owner()->id().c_str());
                  failed = true;
                }
              }
            }
          }
        }
        break;
      case grt::ObjectType:
        child = GrtObjectRef::cast_from(object->get_member(member->name));
        if (child.is_valid())
        {
          if (child->owner() != object && member->owned_object)
          {
            log_error("owner of %s::%s is wrong (expected %s, is %s)\n", GrtNamedObjectRef::cast_from(object)->name().c_str(), member->name.c_str(),
                      object->id().c_str(), child->owner()->id().c_str());
            failed = true;
          }
          if (member->owned_object)
            failed = validate_tree_structure(child) || failed;
        }
        break;
      default:
        break;
    }
    return true;
  }


  /** Validates a GRT object tree for correct owner values
   */
  bool validate_tree_structure(const grt::ObjectRef &object)
  {
    bool failed = false;
    grt::MetaClass *mc = object->get_metaclass();
    mc->foreach_member(boost::bind(validate_member, _1, GrtObjectRef::cast_from(object), failed));
    return !failed;
  }



  TimerActionThread::TimerActionThread(const Action &action, gulong milliseconds) : _action(action), _microseconds(milliseconds * 1000)
  {
    _action_mutex= g_mutex_new();
    _thread= g_thread_create(start, this, FALSE, NULL);
  }

  TimerActionThread::~TimerActionThread()
  {
    g_mutex_free(_action_mutex);
  }

  TimerActionThread * TimerActionThread::create(const Action &action, gulong milliseconds)
  {
    return new TimerActionThread(action, milliseconds);
  }

  gpointer TimerActionThread::start(gpointer data)
  {
    TimerActionThread *thread= static_cast<TimerActionThread*>(data);
    thread->main_loop();
    return NULL;
  }

  void TimerActionThread::stop(bool clear_exit_signal)
  {
    GMutexLock action_mutex(_action_mutex);
    _action= Action();
    if (clear_exit_signal)
      on_exit.disconnect_all_slots();
  }

  void TimerActionThread::main_loop()
  {
    const int poll_interval= 1000000; // check every 1 sec if thread was stopped
    for (;;)
    {
      std::div_t d= std::div(_microseconds, poll_interval);
      for (int n= 0; n < d.quot; ++n)
      {
        g_usleep(poll_interval);
        {
          GMutexLock action_mutex(_action_mutex);
          if (_action.empty())
            goto exit;
        }
      }
      g_usleep(d.rem);
      {
        GMutexLock action_mutex(_action_mutex);
        if (_action.empty())
          goto exit;
        if (_microseconds != 0)
          _action();
        else
          g_usleep(poll_interval);
      }
    }

exit:
    on_exit();
    delete this;
  }


};
