/* 
 * 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
 */
#ifndef _BE_COMMON_H_
#define _BE_COMMON_H_

#include <cstring>
#include <vector>
#include <string>
#include <glib.h>
#include <algorithm>

#ifndef _WIN32
#include <stdexcept>
#include <stdarg.h>
#endif

#include <string.h>
#include "grtpp.h"

#include "wbpublic_public_interface.h"

#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif



namespace bec {
  
  enum MatchType
  {
    MatchAny,
    MatchBefore,
    MatchAfter,
    MatchLast
  };
  
  enum MoveType
  {
    MoveTop,
    MoveUp,
    MoveDown,
    MoveBottom
  };

  enum FindType
  {
    FindPrefix,
    FindFull
  };

  WBPUBLICBACKEND_PUBLIC_FUNC bool validate_tree_structure(const grt::ObjectRef &object);

#define DATETIME_FMT "%Y-%m-%d %H:%M"
  std::string WBPUBLICBACKEND_PUBLIC_FUNC fmttime(time_t t= 0, const char *fmt= "%b %d, %Y");

  std::string WBPUBLICBACKEND_PUBLIC_FUNC replace_string(const std::string &s,
                            const std::string &from,
                            const std::string &to);
  void WBPUBLICBACKEND_PUBLIC_FUNC replace_string_inplace(std::string &text,
                            const std::string &from,
                            const std::string &to);
  
  // replaces a variable from a string in format %variable%
  // a filter can be passed to the variable as in %variable|filter%
  // supported filters are upper, lower and capitalize
  std::string WBPUBLICBACKEND_PUBLIC_FUNC replace_variable(const std::string &format, const std::string &variable, const std::string &value);
  
  std::string WBPUBLICBACKEND_PUBLIC_FUNC append_extension_if_needed(const std::string &path,
                                                              const std::string &ext);

  inline bool has_prefix(const std::string &str, const std::string &pref)
  {
    if (strncmp(str.c_str(), pref.c_str(), pref.length())==0)
      return true;
    return false;
  }


  inline bool has_suffix(const std::string &str, const std::string &suf)
  {
    if (suf.length() < str.length() && strncmp(str.c_str()+str.length()-suf.length(), suf.c_str(), suf.length())==0)
      return true;
    return false;
  }


  std::string WBPUBLICBACKEND_PUBLIC_FUNC make_path(const std::string &prefix, const std::string &file);

  inline std::string pathlist_append(const std::string &l, const std::string &s)
  {
    if (l.empty())
      return s;
    return l+G_SEARCHPATH_SEPARATOR+s;
  }

  inline std::string pathlist_prepend(const std::string &l, const std::string &s)
  {
    if (l.empty())
      return s;
    return s+G_SEARCHPATH_SEPARATOR+l;
  }
  
  template <class T>
  size_t find_list_ref_item_position(grt::ListRef<T> &item_data, std::string& name, MatchType match = MatchAny, grt::Ref<T>* reference = NULL, FindType find_mode = FindPrefix)
  {

    if ((match == MatchBefore || match == MatchAfter) && !reference)
        throw std::invalid_argument("A reference must be specified for MatchBefore and MatchAfter");

    bool search_enabled = match != MatchAfter;
    bool exit = false;

    size_t index = grt::BaseListRef::npos;
    
    for ( grt::TypedListConstIterator<T> end = item_data.end(),
         inst = item_data.begin(); inst != end && !exit; ++inst)
    {
      // If skip is defined will omit the entries until the 'skip' element is found
      if (search_enabled)
      {
        // For MatchBefore the search ends when the reference item is found
        if (match == MatchBefore && (*reference) == (*inst))
          exit = true;
        else
        {
          std::string item_name = (*inst)->name();
          
          int compare_result = (find_mode == FindPrefix) ? item_name.compare(0, name.length(), name) : item_name.compare(name);

          // index will contain always the position of the last matched entry
          if (compare_result == 0)
          {
            index = item_data.get_index(*inst);
            
            // MatchBefore needs to search until the reference is found
            // MatchLast needs to search until the whole list has been searched to get the last match
            // MatchAfter and MatchAny are done as soon as a match is found
            if (match != MatchBefore && match != MatchLast)
              exit = true;
          }
        }
      }
      
      // For MatchAfter the search starts once the reference item has been found
      else if ((*reference) == (*inst))
        search_enabled = true;
    }  
    
    return index;
  }
  
  template <class T>
  void move_list_ref_item(MoveType move_type, grt::ListRef<T> items, const grt::ValueRef &object)
  {
    grt::Type object_type = object.type();
    
    std::string group_name;
    std::string item_name;
    std::string search_name = "";
    size_t group_indicator_position = std::string::npos;
    
    MatchType match = (move_type == MoveUp) ? MatchBefore : MatchAfter;
    
    grt::Ref<T> item;
    
    // Gets the relevant index for the selected object.
    size_t item_index = grt::BaseListRef::npos;
    if (object_type == grt::ObjectType)
    {
      // For any object like connections or instances.
      item = grt::Ref<T>::cast_from(object);
      
      item_index = items.get_index( item );
      item_name = item->name();
      group_indicator_position = item_name.find("/");
      
      // When a grouped item is selected, the movement will be done across same
      // group items
      if (group_indicator_position != std::string::npos)
        search_name = item_name.substr(0, group_indicator_position + 1);
    }
    else
    {
      // For strings (i.e. group names).
      group_name = object.repr();
      group_name += "/";
      
      // Searches the index of the initial element of the group.
      item_index = find_list_ref_item_position<T>(items, group_name);
      item = items[item_index];
      item_name = group_name;
    }
    
    // This is executed whenever the target position depends on the main list
    // The only case where this is excluded is when the selected item belongs to a group
    if (group_indicator_position == std::string::npos)
    {
      std::vector<std::string> items_list;
      
      // Gets the main list items (groups and non grouped items).
      for ( grt::TypedListConstIterator<T> end = items.end(),
           inst = items.begin(); inst != end; ++inst)
      {
        std::string item_name = (*inst)->name();
        size_t position = item_name.find("/");
        
        if (position != std::string::npos)
        {
          std::string group_name = item_name.substr(0, position + 1);
          if (std::find(items_list.begin(), items_list.end(), group_name) == items_list.end())
            items_list.push_back(group_name);
        }
        else
          items_list.push_back(item_name);
      }
        
      // Searches the item inside the list of non grouped items/groups, will find it only if it's a non grouped item
      size_t item_list_position = std::find(items_list.begin(), items_list.end(), std::string(item_name)) - items_list.begin();
      
      if (move_type != MoveTop && move_type != MoveBottom)
      {
        size_t offset = (move_type == MoveUp) ? -1 : 1;

        item_name = items_list.at(item_list_position + offset);

        // If the next item is a group
        group_indicator_position = item_name.find("/");

        if(group_indicator_position != std::string::npos)
        {
          search_name = item_name.substr(0, group_indicator_position + 1);
          if (move_type == MoveUp)
            match = MatchAny;
        }
        else
          search_name = item_name;
      }
    }

    if (move_type == MoveTop)
      items.reorder(item_index, 0);
    else if (move_type == MoveBottom)
      items.reorder(item_index, grt::ListRef<T>::npos);
    else
    {
      // Searches the index of the target position
      size_t target_index = grt::BaseListRef::npos;
      target_index = find_list_ref_item_position<T>(items, search_name, match, &item);    

      if (move_type == MoveDown)
        items.reorder(target_index, item_index);
      else
        items.reorder(item_index, target_index);
    }
  }  

  class WBPUBLICBACKEND_PUBLIC_FUNC TimerActionThread
  {
  public:
    typedef boost::function<void ()> Action;
    static TimerActionThread * create(const Action &action, gulong milliseconds);
    ~TimerActionThread();
    void stop(bool clear_exit_signal);
    boost::signals2::signal<void ()> on_exit;
  private:
    GMutex *_action_mutex;
    Action _action;
    gulong _microseconds;
    GThread *_thread;
    TimerActionThread(const Action &action, gulong milliseconds);
    static gpointer start(gpointer data);
    void main_loop();
  };

  class WBPUBLICBACKEND_PUBLIC_FUNC ScopeExitTrigger
  {
  public:
    typedef boost::function<void ()> Slot;
    ScopeExitTrigger() {}
    ScopeExitTrigger(const Slot &cb) : slot(cb) {}
    ~ScopeExitTrigger() { slot(); }
    ScopeExitTrigger & operator=(const Slot &cb) { slot= cb; return *this; }
    Slot slot;
  };
  
};

#endif /* _BE_COMMON_H_ */
