#pragma once
/*
 *      Copyright (C) 2005-2010 Team XBMC
 *      http://www.xbmc.org
 *
 *  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; either version 2, or (at your option)
 *  any later version.
 *
 *  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 XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include <string>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>

#ifdef _WIN32                   // windows
#include "dlfcn-win32.h"
#define ADDON_DLL               "\\library.xbmc.addon\\libXBMC_addon" ADDON_HELPER_EXT
#define ADDON_HELPER_EXT        ".dll"
#else
#if defined(__APPLE__)          // osx
#if defined(__POWERPC__)
#define ADDON_HELPER_ARCH       "powerpc-osx"
#elif defined(__arm__)
#define ADDON_HELPER_ARCH       "arm-osx"
#elif defined(__x86_64__)
#define ADDON_HELPER_ARCH       "x86-osx"
#else
#define ADDON_HELPER_ARCH       "x86-osx"
#endif
#else                           // linux
#if defined(__x86_64__)
#define ADDON_HELPER_ARCH       "x86_64-linux"
#elif defined(_POWERPC)
#define ADDON_HELPER_ARCH       "powerpc-linux"
#elif defined(_POWERPC64)
#define ADDON_HELPER_ARCH       "powerpc64-linux"
#elif defined(__ARMEL__)
#define ADDON_HELPER_ARCH       "arm"
#elif defined(_MIPSEL)
#define ADDON_HELPER_ARCH       "mipsel-linux"
#else
#define ADDON_HELPER_ARCH       "i486-linux"
#endif
#endif
#include <dlfcn.h>              // linux+osx
#define ADDON_HELPER_EXT        ".so"
#define ADDON_DLL_NAME "libXBMC_addon-" ADDON_HELPER_ARCH ADDON_HELPER_EXT
#define ADDON_DLL "/library.xbmc.addon/" ADDON_DLL_NAME
#endif
#if defined(ANDROID)
#include <sys/stat.h>
#endif

#ifdef LOG_DEBUG
#undef LOG_DEBUG
#endif
#ifdef LOG_INFO
#undef LOG_INFO
#endif
#ifdef LOG_NOTICE
#undef LOG_NOTICE
#endif
#ifdef LOG_ERROR
#undef LOG_ERROR
#endif

namespace ADDON
{
  typedef enum addon_log
  {
    LOG_DEBUG,
    LOG_INFO,
    LOG_NOTICE,
    LOG_ERROR
  } addon_log_t;

  typedef enum queue_msg
  {
    QUEUE_INFO,
    QUEUE_WARNING,
    QUEUE_ERROR
  } queue_msg_t;

  class CHelper_libXBMC_addon
  {
  public:
    CHelper_libXBMC_addon()
    {
      m_libXBMC_addon = NULL;
      m_Handle        = NULL;
    }

    ~CHelper_libXBMC_addon()
    {
      if (m_libXBMC_addon)
      {
        XBMC_unregister_me(m_Handle, m_Callbacks);
        dlclose(m_libXBMC_addon);
      }
    }

    bool RegisterMe(void *Handle)
    {
      m_Handle = Handle;

      std::string libBasePath;
      libBasePath  = ((cb_array*)m_Handle)->libPath;
      libBasePath += ADDON_DLL;

#if defined(ANDROID)
      struct stat st;
      if(stat(libBasePath.c_str(),&st) != 0)
      {
        std::string tempbin = getenv("XBMC_ANDROID_LIBS");
        libBasePath = tempbin + "/" + ADDON_DLL_NAME;
      }
#endif

      m_libXBMC_addon = dlopen(libBasePath.c_str(), RTLD_LAZY);
      if (m_libXBMC_addon == NULL)
      {
        fprintf(stderr, "Unable to load %s\n", dlerror());
        return false;
      }

      XBMC_register_me = (void* (*)(void *HANDLE))
        dlsym(m_libXBMC_addon, "XBMC_register_me");
      if (XBMC_register_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_unregister_me = (void (*)(void* HANDLE, void* CB))
        dlsym(m_libXBMC_addon, "XBMC_unregister_me");
      if (XBMC_unregister_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_log = (void (*)(void* HANDLE, void* CB, const addon_log_t loglevel, const char *msg))
        dlsym(m_libXBMC_addon, "XBMC_log");
      if (XBMC_log == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_setting = (bool (*)(void* HANDLE, void* CB, const char* settingName, void *settingValue))
        dlsym(m_libXBMC_addon, "XBMC_get_setting");
      if (XBMC_get_setting == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_queue_notification = (void (*)(void* HANDLE, void* CB, const queue_msg_t loglevel, const char *msg))
        dlsym(m_libXBMC_addon, "XBMC_queue_notification");
      if (XBMC_queue_notification == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_unknown_to_utf8 = (char* (*)(void* HANDLE, void* CB, const char* str))
        dlsym(m_libXBMC_addon, "XBMC_unknown_to_utf8");
      if (XBMC_unknown_to_utf8 == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_localized_string = (char* (*)(void* HANDLE, void* CB, int dwCode))
        dlsym(m_libXBMC_addon, "XBMC_get_localized_string");
      if (XBMC_get_localized_string == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_free_string = (void (*)(void* HANDLE, void* CB, char* str))
        dlsym(m_libXBMC_addon, "XBMC_free_string");
      if (XBMC_free_string == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_dvd_menu_language = (char* (*)(void* HANDLE, void* CB))
        dlsym(m_libXBMC_addon, "XBMC_get_dvd_menu_language");
      if (XBMC_get_dvd_menu_language == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_open_file = (void* (*)(void* HANDLE, void* CB, const char* strFileName, unsigned int flags))
        dlsym(m_libXBMC_addon, "XBMC_open_file");
      if (XBMC_open_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_open_file_for_write = (void* (*)(void* HANDLE, void* CB, const char* strFileName, bool bOverWrite))
        dlsym(m_libXBMC_addon, "XBMC_open_file_for_write");
      if (XBMC_open_file_for_write == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_read_file = (unsigned int (*)(void* HANDLE, void* CB, void* file, void* lpBuf, int64_t uiBufSize))
        dlsym(m_libXBMC_addon, "XBMC_read_file");
      if (XBMC_read_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_read_file_string = (bool (*)(void* HANDLE, void* CB, void* file, char *szLine, int iLineLength))
        dlsym(m_libXBMC_addon, "XBMC_read_file_string");
      if (XBMC_read_file_string == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_write_file = (int (*)(void* HANDLE, void* CB, void* file, const void* lpBuf, int64_t uiBufSize))
        dlsym(m_libXBMC_addon, "XBMC_write_file");
      if (XBMC_write_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_flush_file = (void (*)(void* HANDLE, void* CB, void* file))
        dlsym(m_libXBMC_addon, "XBMC_flush_file");
      if (XBMC_flush_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_seek_file = (int64_t (*)(void* HANDLE, void* CB, void* file, int64_t iFilePosition, int iWhence))
        dlsym(m_libXBMC_addon, "XBMC_seek_file");
      if (XBMC_seek_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_truncate_file = (int (*)(void* HANDLE, void* CB, void* file, int64_t iSize))
        dlsym(m_libXBMC_addon, "XBMC_truncate_file");
      if (XBMC_truncate_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_file_position = (int64_t (*)(void* HANDLE, void* CB, void* file))
        dlsym(m_libXBMC_addon, "XBMC_get_file_position");
      if (XBMC_get_file_position == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_file_length = (int64_t (*)(void* HANDLE, void* CB, void* file))
        dlsym(m_libXBMC_addon, "XBMC_get_file_length");
      if (XBMC_get_file_length == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_close_file = (void (*)(void* HANDLE, void* CB, void* file))
        dlsym(m_libXBMC_addon, "XBMC_close_file");
      if (XBMC_close_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_get_file_chunk_size = (int (*)(void* HANDLE, void* CB, void* file))
        dlsym(m_libXBMC_addon, "XBMC_get_file_chunk_size");
      if (XBMC_get_file_chunk_size == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_file_exists = (bool (*)(void* HANDLE, void* CB, const char *strFileName, bool bUseCache))
        dlsym(m_libXBMC_addon, "XBMC_file_exists");
      if (XBMC_file_exists == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_stat_file = (int (*)(void* HANDLE, void* CB, const char *strFileName, struct __stat64* buffer))
        dlsym(m_libXBMC_addon, "XBMC_stat_file");
      if (XBMC_stat_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_delete_file = (bool (*)(void* HANDLE, void* CB, const char *strFileName))
        dlsym(m_libXBMC_addon, "XBMC_delete_file");
      if (XBMC_delete_file == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_can_open_directory = (bool (*)(void* HANDLE, void* CB, const char* strURL))
        dlsym(m_libXBMC_addon, "XBMC_can_open_directory");
      if (XBMC_can_open_directory == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_create_directory = (bool (*)(void* HANDLE, void* CB, const char* strPath))
        dlsym(m_libXBMC_addon, "XBMC_create_directory");
      if (XBMC_create_directory == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_directory_exists = (bool (*)(void* HANDLE, void* CB, const char* strPath))
        dlsym(m_libXBMC_addon, "XBMC_directory_exists");
      if (XBMC_directory_exists == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      XBMC_remove_directory = (bool (*)(void* HANDLE, void* CB, const char* strPath))
        dlsym(m_libXBMC_addon, "XBMC_remove_directory");
      if (XBMC_remove_directory == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; }

      m_Callbacks = XBMC_register_me(m_Handle);
      return m_Callbacks != NULL;
    }

    /*!
     * @brief Add a message to XBMC's log.
     * @param loglevel The log level of the message.
     * @param format The format of the message to pass to XBMC.
     */
    void Log(const addon_log_t loglevel, const char *format, ... )
    {
      char buffer[16384];
      va_list args;
      va_start (args, format);
      vsprintf (buffer, format, args);
      va_end (args);
      return XBMC_log(m_Handle, m_Callbacks, loglevel, buffer);
    }

    /*!
     * @brief Get a settings value for this add-on.
     * @param settingName The name of the setting to get.
     * @param settingValue The value.
     * @return True if the settings was fetched successfully, false otherwise.
     */
    bool GetSetting(const char* settingName, void *settingValue)
    {
      return XBMC_get_setting(m_Handle, m_Callbacks, settingName, settingValue);
    }

    /*!
     * @brief Queue a notification in the GUI.
     * @param type The message type.
     * @param format The format of the message to pass to display in XBMC.
     */
    void QueueNotification(const queue_msg_t type, const char *format, ... )
    {
      char buffer[16384];
      va_list args;
      va_start (args, format);
      vsprintf (buffer, format, args);
      va_end (args);
      return XBMC_queue_notification(m_Handle, m_Callbacks, type, buffer);
    }

    /*!
     * @brief Translate a string with an unknown encoding to UTF8.
     * @param str The string to translate.
     * @return The string translated to UTF8. Must be freed by calling FreeString() when done.
     */
    char* UnknownToUTF8(const char* str)
    {
      return XBMC_unknown_to_utf8(m_Handle, m_Callbacks, str);
    }

    /*!
     * @brief Get a localised message.
     * @param dwCode The code of the message to get.
     * @return The message. Must be freed by calling FreeString() when done.
     */
    char* GetLocalizedString(int dwCode)
    {
      return XBMC_get_localized_string(m_Handle, m_Callbacks, dwCode);
    }


    /*!
     * @brief Get the DVD menu language.
     * @return The language. Must be freed by calling FreeString() when done.
     */
    char* GetDVDMenuLanguage()
    {
      return XBMC_get_dvd_menu_language(m_Handle, m_Callbacks);
    }

    /*!
     * @brief Free the memory used by str
     * @param str The string to free
     */
    void FreeString(char* str)
    {
      return XBMC_free_string(m_Handle, m_Callbacks, str);
    }

    /*!
     * @brief Open the file with filename via XBMC's CFile. Needs to be closed by calling CloseFile() when done.
     * @param strFileName The filename to open.
     * @param flags The flags to pass. Documented in XBMC's File.h
     * @return A handle for the file, or NULL if it couldn't be opened.
     */
    void* OpenFile(const char* strFileName, unsigned int flags)
    {
      return XBMC_open_file(m_Handle, m_Callbacks, strFileName, flags);
    }

    /*!
     * @brief Open the file with filename via XBMC's CFile in write mode. Needs to be closed by calling CloseFile() when done.
     * @param strFileName The filename to open.
     * @param bOverWrite True to overwrite, false otherwise.
     * @return A handle for the file, or NULL if it couldn't be opened.
     */
    void* OpenFileForWrite(const char* strFileName, bool bOverWrite)
    {
      return XBMC_open_file_for_write(m_Handle, m_Callbacks, strFileName, bOverWrite);
    }

    /*!
     * @brief Read from an open file.
     * @param file The file handle to read from.
     * @param lpBuf The buffer to store the data in.
     * @param uiBufSize The size of the buffer.
     * @return Number of bytes read.
     */
    unsigned int ReadFile(void* file, void* lpBuf, int64_t uiBufSize)
    {
      return XBMC_read_file(m_Handle, m_Callbacks, file, lpBuf, uiBufSize);
    }

    /*!
     * @brief Read a string from an open file.
     * @param file The file handle to read from.
     * @param szLine The buffer to store the data in.
     * @param iLineLength The size of the buffer.
     * @return True when a line was read, false otherwise.
     */
    bool ReadFileString(void* file, char *szLine, int iLineLength)
    {
      return XBMC_read_file_string(m_Handle, m_Callbacks, file, szLine, iLineLength);
    }

    /*!
     * @brief Write to a file opened in write mode.
     * @param file The file handle to write to.
     * @param lpBuf The data to write.
     * @param uiBufSize Size of the data to write.
     * @return The number of bytes read.
     */
    int WriteFile(void* file, const void* lpBuf, int64_t uiBufSize)
    {
      return XBMC_write_file(m_Handle, m_Callbacks, file, lpBuf, uiBufSize);
    }

    /*!
     * @brief Flush buffered data.
     * @param file The file handle to flush the data for.
     */
    void FlushFile(void* file)
    {
       return XBMC_flush_file(m_Handle, m_Callbacks, file);
    }

    /*!
     * @brief Seek in an open file.
     * @param file The file handle to see in.
     * @param iFilePosition The new position.
     * @param iWhence Seek argument. See stdio.h for possible values.
     * @return The new position.
     */
    int64_t SeekFile(void* file, int64_t iFilePosition, int iWhence)
    {
      return XBMC_seek_file(m_Handle, m_Callbacks, file, iFilePosition, iWhence);
    }

    /*!
     * @brief Truncate a file to the requested size.
     * @param file The file handle to truncate.
     * @param iSize The new max size.
     * @return New size?
     */
    int TruncateFile(void* file, int64_t iSize)
    {
      return XBMC_truncate_file(m_Handle, m_Callbacks, file, iSize);
    }

    /*!
     * @brief The current position in an open file.
     * @param file The file handle to get the position for.
     * @return The requested position.
     */
    int64_t GetFilePosition(void* file)
    {
      return XBMC_get_file_position(m_Handle, m_Callbacks, file);
    }

    /*!
     * @brief Get the file size of an open file.
     * @param file The file to get the size for.
     * @return The requested size.
     */
    int64_t GetFileLength(void* file)
    {
      return XBMC_get_file_length(m_Handle, m_Callbacks, file);
    }

    /*!
     * @brief Close an open file.
     * @param file The file handle to close.
     */
    void CloseFile(void* file)
    {
      return XBMC_close_file(m_Handle, m_Callbacks, file);
    }

    /*!
     * @brief Get the chunk size for an open file.
     * @param file the file handle to get the size for.
     * @return The requested size.
     */
    int GetFileChunkSize(void* file)
    {
      return XBMC_get_file_chunk_size(m_Handle, m_Callbacks, file);
    }

    /*!
     * @brief Check if a file exists.
     * @param strFileName The filename to check.
     * @param bUseCache Check in file cache.
     * @return true if the file exists false otherwise.
     */
    bool FileExists(const char *strFileName, bool bUseCache)
    {
      return XBMC_file_exists(m_Handle, m_Callbacks, strFileName, bUseCache);
    }

    /*!
     * @brief Reads file status.
     * @param strFileName The filename to read the status from.
     * @param buffer The file status is written into this buffer.
     * @return The file status was successfully read.
     */
    int StatFile(const char *strFileName, struct __stat64* buffer)
    {
      return XBMC_stat_file(m_Handle, m_Callbacks, strFileName, buffer);
    }

    /*!
     * @brief Deletes a file.
     * @param strFileName The filename to delete.
     * @return The file was successfully deleted.
     */
    bool DeleteFile(const char *strFileName)
    {
      return XBMC_delete_file(m_Handle, m_Callbacks, strFileName);
    }

    /*!
     * @brief Checks whether a directory can be opened.
     * @param strUrl The URL of the directory to check.
     * @return True when it can be opened, false otherwise.
     */
    bool CanOpenDirectory(const char* strUrl)
    {
      return XBMC_can_open_directory(m_Handle, m_Callbacks, strUrl);
    }

    /*!
     * @brief Creates a directory.
     * @param strPath Path to the directory.
     * @return True when it was created, false otherwise.
     */
    bool CreateDirectory(const char *strPath)
    {
      return XBMC_create_directory(m_Handle, m_Callbacks, strPath);
    }

    /*!
     * @brief Checks if a directory exists.
     * @param strPath Path to the directory.
     * @return True when it exists, false otherwise.
     */
    bool DirectoryExists(const char *strPath)
    {
      return XBMC_directory_exists(m_Handle, m_Callbacks, strPath);
    }

    /*!
     * @brief Removes a directory.
     * @param strPath Path to the directory.
     * @return True when it was removed, false otherwise.
     */
    bool RemoveDirectory(const char *strPath)
    {
      return XBMC_remove_directory(m_Handle, m_Callbacks, strPath);
    }

  protected:
    void* (*XBMC_register_me)(void *HANDLE);
    void (*XBMC_unregister_me)(void *HANDLE, void* CB);
    void (*XBMC_log)(void *HANDLE, void* CB, const addon_log_t loglevel, const char *msg);
    bool (*XBMC_get_setting)(void *HANDLE, void* CB, const char* settingName, void *settingValue);
    void (*XBMC_queue_notification)(void *HANDLE, void* CB, const queue_msg_t type, const char *msg);
    char* (*XBMC_unknown_to_utf8)(void *HANDLE, void* CB, const char* str);
    char* (*XBMC_get_localized_string)(void *HANDLE, void* CB, int dwCode);
    char* (*XBMC_get_dvd_menu_language)(void *HANDLE, void* CB);
    void (*XBMC_free_string)(void *HANDLE, void* CB, char* str);
    void* (*XBMC_open_file)(void *HANDLE, void* CB, const char* strFileName, unsigned int flags);
    void* (*XBMC_open_file_for_write)(void *HANDLE, void* CB, const char* strFileName, bool bOverWrite);
    unsigned int (*XBMC_read_file)(void *HANDLE, void* CB, void* file, void* lpBuf, int64_t uiBufSize);
    bool (*XBMC_read_file_string)(void *HANDLE, void* CB, void* file, char *szLine, int iLineLength);
    int (*XBMC_write_file)(void *HANDLE, void* CB, void* file, const void* lpBuf, int64_t uiBufSize);
    void (*XBMC_flush_file)(void *HANDLE, void* CB, void* file);
    int64_t (*XBMC_seek_file)(void *HANDLE, void* CB, void* file, int64_t iFilePosition, int iWhence);
    int (*XBMC_truncate_file)(void *HANDLE, void* CB, void* file, int64_t iSize);
    int64_t (*XBMC_get_file_position)(void *HANDLE, void* CB, void* file);
    int64_t (*XBMC_get_file_length)(void *HANDLE, void* CB, void* file);
    void (*XBMC_close_file)(void *HANDLE, void* CB, void* file);
    int (*XBMC_get_file_chunk_size)(void *HANDLE, void* CB, void* file);
    bool (*XBMC_file_exists)(void *HANDLE, void* CB, const char *strFileName, bool bUseCache);
    int (*XBMC_stat_file)(void *HANDLE, void* CB, const char *strFileName, struct __stat64* buffer);
    bool (*XBMC_delete_file)(void *HANDLE, void* CB, const char *strFileName);
    bool (*XBMC_can_open_directory)(void *HANDLE, void* CB, const char* strURL);
    bool (*XBMC_create_directory)(void *HANDLE, void* CB, const char* strPath);
    bool (*XBMC_directory_exists)(void *HANDLE, void* CB, const char* strPath);
    bool (*XBMC_remove_directory)(void *HANDLE, void* CB, const char* strPath);

  private:
    void *m_libXBMC_addon;
    void *m_Handle;
    void *m_Callbacks;
    struct cb_array
    {
      const char* libPath;
    };
  };
};
