//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2017 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    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 Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

#ifndef OPENVPN_APPLECRYPTO_CF_CFHELPER_H
#define OPENVPN_APPLECRYPTO_CF_CFHELPER_H

#include <openvpn/buffer/buffer.hpp>
#include <openvpn/apple/cf/cf.hpp>

// These methods build on the Wrapper classes for Apple Core Foundation objects
// defined in cf.hpp.  They add additional convenience methods, such as dictionary
// lookup.

namespace openvpn {
  namespace CF {

    // essentially a vector of void *, used as source for array and dictionary constructors
    typedef BufferAllocatedType<CFTypeRef, thread_unsafe_refcount> SrcList;

    inline Array array(const SrcList& values)
    {
      return array((const void **)values.c_data(), values.size());
    }

    inline Dict dict(const SrcList& keys, const SrcList& values)
    {
      return dict((const void **)keys.c_data(), (const void **)values.c_data(), std::min(keys.size(), values.size()));
    }

    inline CFTypeRef mutable_dict_new()
    {
      return CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    }

    inline CFTypeRef mutable_array_new()
    {
      return CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    }

    // Lookup or create (if absent) an item in a mutable dictionary.
    // Return the item, which will be owned by base.
    template <typename KEY>
    inline CFTypeRef dict_get_create(CFMutableDictionaryRef base,
				     const KEY& key,
				     CFTypeRef (*create_method)())
    {
      if (base)
	{
	  String keystr = string(key);
	  CFTypeRef ret = CFDictionaryGetValue(base, keystr()); // try lookup first
	  if (!ret)
	    {
	      // doesn't exist, must create
	      ret = (*create_method)();
	      CFDictionaryAddValue(base, keystr(), ret);
	      CFRelease(ret); // because ret is now owned by base
	    }
	  return ret;
	}
      return nullptr;
    }

    // lookup a dict in another dict (base) and return or create if absent
    template <typename KEY>
    inline MutableDict dict_get_create_dict(MutableDict& base, const KEY& key)
    {
      String keystr = string(key);
      return mutable_dict_cast(dict_get_create(base(), keystr(), mutable_dict_new));
    }

    // lookup an array in a dict (base) and return or create if absent
    template <typename KEY>
    inline MutableArray dict_get_create_array(MutableDict& base, const KEY& key)
    {
      String keystr = string(key);
      return mutable_array_cast(dict_get_create(base(), keystr(), mutable_array_new));
    }

    // lookup an object in a dictionary (DICT should be a Dict or a MutableDict)
    template <typename DICT, typename KEY>
    inline CFTypeRef dict_get_obj(const DICT& dict, const KEY& key)
    {
      return dict_index(dict, key);
    }

    // lookup a string in a dictionary (DICT should be a Dict or a MutableDict)
    template <typename DICT, typename KEY>
    inline std::string dict_get_str(const DICT& dict, const KEY& key)
    {
      return cppstring(string_cast(dict_index(dict, key)));
    }

    // lookup a string in a dictionary (DICT should be a Dict or a MutableDict)
    template <typename DICT, typename KEY>
    inline std::string dict_get_str(const DICT& dict, const KEY& key, const std::string& default_value)
    {
      String str(string_cast(dict_index(dict, key)));
      if (str.defined())
	return cppstring(str());
      else
	return default_value;
    }

    // lookup an integer in a dictionary (DICT should be a Dict or a MutableDict)
    template <typename DICT, typename KEY>
    inline int dict_get_int(const DICT& dict, const KEY& key, const int default_value)
    {
      int ret;
      Number num = number_cast(dict_index(dict, key));
      if (num.defined() && CFNumberGetValue(num(), kCFNumberIntType, &ret))
	return ret;
      else
	return default_value;
    }

    // lookup a boolean in a dictionary (DICT should be a Dict or a MutableDict)
    template <typename DICT, typename KEY>
    inline bool dict_get_bool(const DICT& dict, const KEY& key, const bool default_value)
    {
      Bool b = bool_cast(dict_index(dict, key));
      if (b.defined())
	{
	  if (b() == kCFBooleanTrue)
	    return true;
	  else if (b() == kCFBooleanFalse)
	    return false;
	}
      return default_value;
    }

    // like CFDictionarySetValue, but no-op if any args are NULL
    inline void dictionarySetValue(CFMutableDictionaryRef theDict, const void *key, const void *value)
    {
      if (theDict && key && value)
	CFDictionarySetValue(theDict, key, value);
    }

    // like CFArrayAppendValue, but no-op if any args are NULL
    inline void arrayAppendValue(CFMutableArrayRef theArray, const void *value)
    {
      if (theArray && value)
	CFArrayAppendValue(theArray, value);
    }

    // set a CFTypeRef in a mutable dictionary
    template <typename KEY>
    inline void dict_set_obj(MutableDict& dict, const KEY& key, CFTypeRef value)
    {
      String keystr = string(key);
      dictionarySetValue(dict(), keystr(), value);
    }

    // set a string in a mutable dictionary

    template <typename KEY, typename VALUE>
    inline void dict_set_str(MutableDict& dict, const KEY& key, const VALUE& value)
    {
      String keystr = string(key);
      String valstr = string(value);
      dictionarySetValue(dict(), keystr(), valstr());
    }

    // set a number in a mutable dictionary

    template <typename KEY>
    inline void dict_set_int(MutableDict& dict, const KEY& key, int value)
    {
      String keystr = string(key);
      Number num = number_from_int(value);
      dictionarySetValue(dict(), keystr(), num());
    }

    template <typename KEY>
    inline void dict_set_int32(MutableDict& dict, const KEY& key, SInt32 value)
    {
      String keystr = string(key);
      Number num = number_from_int32(value);
      dictionarySetValue(dict(), keystr(), num());
    }

    template <typename KEY>
    inline void dict_set_long_long(MutableDict& dict, const KEY& key, long long value)
    {
      String keystr = string(key);
      Number num = number_from_long_long(value);
      dictionarySetValue(dict(), keystr(), num());
    }

    template <typename KEY>
    inline void dict_set_index(MutableDict& dict, const KEY& key, CFIndex value)
    {
      String keystr = string(key);
      Number num = number_from_index(value);
      dictionarySetValue((CFMutableDictionaryRef)dict(), keystr(), num());
    }

    // set a boolean in a mutable dictionary

    template <typename KEY>
    inline void dict_set_bool(MutableDict& dict, const KEY& key, bool value)
    {
      String keystr = string(key);
      CFBooleanRef boolref = value ? kCFBooleanTrue : kCFBooleanFalse;
      dictionarySetValue(dict(), keystr(), boolref);
    }

    // append string to a mutable array

    template <typename VALUE>
    inline void array_append_str(MutableArray& array, const VALUE& value)
    {
      String valstr = string(value);
      arrayAppendValue(array(), valstr());
    }

    // append a number to a mutable array

    inline void array_append_int(MutableArray& array, int value)
    {
      Number num = number_from_int(value);
      arrayAppendValue(array(), num());
    }

    inline void array_append_int32(MutableArray& array, SInt32 value)
    {
      Number num = number_from_int32(value);
      arrayAppendValue(array(), num());
    }

    inline void array_append_long_long(MutableArray& array, long long value)
    {
      Number num = number_from_long_long(value);
      arrayAppendValue(array(), num());
    }

    inline void array_append_index(MutableArray& array, CFIndex value)
    {
      Number num = number_from_index(value);
      arrayAppendValue(array(), num());
    }
  }
}
#endif
