text: ﻿#ifndef MPVTOOL_H
  #define MPVTOOL_H
  
  #include <client.h>
  
  #include <cstring>
  #include <QVariant>
  #include <QString>
  #include <QList>
  #include <QHash>
  #include <QSharedPointer>
  #include <QMetaType>
  
  namespace qtmpv {
  
  // Wrapper around mpv_handle. Does refcounting under the hood.
  class Handle
  {
      struct container {
          container(mpv_handle *h) : mpv(h) {}
          ~container() {
              mpv_terminate_destroy(mpv);
          }
          mpv_handle *mpv;
      };
      QSharedPointer<container> sptr;
  public:
      // Construct a new Handle from a raw mpv_handle with refcount 1. If the
      // last Handle goes out of scope, the mpv_handle will be destroyed with
      // mpv_terminate_destroy().
      // Never destroy the mpv_handle manually when using this wrapper. You
      // will create dangling pointers. Just let the wrapper take care of
      // destroying the mpv_handle.
      // Never create multiple wrappers from the same raw mpv_handle; copy the
      // wrapper instead (that's what it's for).
      static Handle FromRawHandle(mpv_handle *handle) {
          Handle h;
          h.sptr = QSharedPointer<container>(new container(handle));
          return h;
      }
  
      // Return the raw handle; for use with the libmpv C API.
      operator mpv_handle *() const {
          return sptr ? (*sptr).mpv : 0;
      }
  };
  
  static inline QVariant node_to_variant(const mpv_node *node)
  {
      switch (node->format) {
          case MPV_FORMAT_STRING:
              return QVariant(QString::fromUtf8(node->u.string));
          case MPV_FORMAT_FLAG:
              return QVariant(static_cast<bool>(node->u.flag));
          case MPV_FORMAT_INT64:
              return QVariant(static_cast<qlonglong>(node->u.int64));
          case MPV_FORMAT_DOUBLE:
              return QVariant(node->u.double_);
          case MPV_FORMAT_NODE_ARRAY: {
              mpv_node_list *list = node->u.list;
              QVariantList qlist;
              for (int n = 0; n < list->num; n++) {
                  qlist.append(node_to_variant(&list->values[n]));
              }
              return QVariant(qlist);
          }
          case MPV_FORMAT_NODE_MAP: {
              mpv_node_list *list = node->u.list;
              QVariantMap qmap;
              for (int n = 0; n < list->num; n++) {
                  qmap.insert(QString::fromUtf8(list->keys[n]),
                              node_to_variant(&list->values[n]));
              }
              return QVariant(qmap);
          }
          default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
              return QVariant();
      }
  }
  
  struct node_builder {
      node_builder(const QVariant &v) {
          set(&node_, v);
      }
      ~node_builder() {
          free_node(&node_);
      }
      mpv_node *node() {
          return &node_;
      }
  private:
      Q_DISABLE_COPY(node_builder)
      mpv_node node_;
      mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) {
          dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
          mpv_node_list *list = new mpv_node_list();
          dst->u.list = list;
          if (!list) {
              goto err;
          }
          list->values = new mpv_node[num]();
          if (!list->values) {
              goto err;
          }
          if (is_map) {
              list->keys = new char *[num]();
              if (!list->keys) {
                  goto err;
              }
          }
          return list;
      err:
          free_node(dst);
          return NULL;
      }
      char *dup_qstring(const QString &s) {
          QByteArray b = s.toUtf8();
          char *r = new char[b.size() + 1];
          if (r) {
              std::memcpy(r, b.data(), b.size() + 1);
          }
          return r;
      }
      bool test_type(const QVariant &v, QMetaType::Type t) {
          // The Qt docs say: "Although this function is declared as returning
          // "QVariant::Type(obsolete), the return value should be interpreted
          // as QMetaType::Type."
          // So a cast really seems to be needed to avoid warnings (urgh).
          return static_cast<int>(v.type()) == static_cast<int>(t);
      }
      void set(mpv_node *dst, const QVariant &src) {
          if (test_type(src, QMetaType::QString)) {
              dst->format = MPV_FORMAT_STRING;
              dst->u.string = dup_qstring(src.toString());
              if (!dst->u.string) {
                  goto fail;
              }
          } else if (test_type(src, QMetaType::Bool)) {
              dst->format = MPV_FORMAT_FLAG;
              dst->u.flag = src.toBool() ? 1 : 0;
          } else if (test_type(src, QMetaType::Int) ||
                     test_type(src, QMetaType::LongLong) ||
                     test_type(src, QMetaType::UInt) ||
                     test_type(src, QMetaType::ULongLong)) {
              dst->format = MPV_FORMAT_INT64;
              dst->u.int64 = src.toLongLong();
          } else if (test_type(src, QMetaType::Double)) {
              dst->format = MPV_FORMAT_DOUBLE;
              dst->u.double_ = src.toDouble();
          } else if (src.canConvert<QVariantList>()) {
              QVariantList qlist = src.toList();
              mpv_node_list *list = create_list(dst, false, qlist.size());
              if (!list) {
                  goto fail;
              }
              list->num = qlist.size();
              for (int n = 0; n < qlist.size(); n++) {
                  set(&list->values[n], qlist[n]);
              }
          } else if (src.canConvert<QVariantMap>()) {
              QVariantMap qmap = src.toMap();
              mpv_node_list *list = create_list(dst, true, qmap.size());
              if (!list) {
                  goto fail;
              }
              list->num = qmap.size();
              for (int n = 0; n < qmap.size(); n++) {
                  list->keys[n] = dup_qstring(qmap.keys()[n]);
                  if (!list->keys[n]) {
                      free_node(dst);
                      goto fail;
                  }
                  set(&list->values[n], qmap.values()[n]);
              }
          } else {
              goto fail;
          }
          return;
      fail:
          dst->format = MPV_FORMAT_NONE;
      }
      void free_node(mpv_node *dst) {
          switch (dst->format) {
              case MPV_FORMAT_STRING:
                  delete[] dst->u.string;
                  break;
              case MPV_FORMAT_NODE_ARRAY:
              case MPV_FORMAT_NODE_MAP: {
                  mpv_node_list *list = dst->u.list;
                  if (list) {
                      for (int n = 0; n < list->num; n++) {
                          if (list->keys) {
                              delete[] list->keys[n];
                          }
                          if (list->values) {
                              free_node(&list->values[n]);
                          }
                      }
                      delete[] list->keys;
                      delete[] list->values;
                  }
                  delete list;
                  break;
              }
              default:
                  ;
          }
          dst->format = MPV_FORMAT_NONE;
      }
  };
  
  /**
   * RAII wrapper that calls mpv_free_node_contents() on the pointer.
   */
  struct node_autofree {
      mpv_node *ptr;
      node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {}
      ~node_autofree() {
          mpv_free_node_contents(ptr);
      }
  };
  
  /**
   * Return the given property as mpv_node converted to QVariant, or QVariant()
   * on error.
   *
   * @deprecated use get_property() instead
   *
   * @param name the property name
   */
  static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name)
  {
      mpv_node node;
      if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) {
          return QVariant();
      }
      node_autofree f(&node);
      return node_to_variant(&node);
  }
  
  /**
   * Set the given property as mpv_node converted from the QVariant argument.
  
   * @deprecated use set_property() instead
   */
  static inline int set_property_variant(mpv_handle *ctx, const QString &name,
                                         const QVariant &v)
  {
      node_builder node(v);
      return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
  }
  
  /**
   * Set the given option as mpv_node converted from the QVariant argument.
   *
   * @deprecated use set_property() instead
   */
  static inline int set_option_variant(mpv_handle *ctx, const QString &name,
                                       const QVariant &v)
  {
      node_builder node(v);
      return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
  }
  
  /**
   * mpv_command_node() equivalent. Returns QVariant() on error (and
   * unfortunately, the same on success).
   *
   * @deprecated use command() instead
   */
  static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args)
  {
      node_builder node(args);
      mpv_node res;
      if (mpv_command_node(ctx, node.node(), &res) < 0) {
          return QVariant();
      }
      node_autofree f(&res);
      return node_to_variant(&res);
  }
  
  /**
   * This is used to return error codes wrapped in QVariant for functions which
   * return QVariant.
   *
   * You can use get_error() or is_error() to extract the error status from a
   * QVariant value.
   */
  struct ErrorReturn {
      /**
       * enum mpv_error value (or a value outside of it if ABI was extended)
       */
      int error;
  
      ErrorReturn() : error(0) {}
      explicit ErrorReturn(int err) : error(err) {}
  };
  
  /**
   * Return the mpv error code packed into a QVariant, or 0 (success) if it's not
   * an error value.
   *
   * @return error code (<0) or success (>=0)
   */
  static inline int get_error(const QVariant &v)
  {
      if (!v.canConvert<ErrorReturn>()) {
          return 0;
      }
      return v.value<ErrorReturn>().error;
  }
  
  /**
   * Return whether the QVariant carries a mpv error code.
   */
  static inline bool is_error(const QVariant &v)
  {
      return get_error(v) < 0;
  }
  
  /**
   * Return the given property as mpv_node converted to QVariant, or QVariant()
   * on error.
   *
   * @param name the property name
   * @return the property value, or an ErrorReturn with the error code
   */
  static inline QVariant get_property(mpv_handle *ctx, const QString &name)
  {
      mpv_node node;
      int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
      if (err < 0) {
          return QVariant::fromValue(ErrorReturn(err));
      }
      node_autofree f(&node);
      return node_to_variant(&node);
  }
  
  /**
   * Set the given property as mpv_node converted from the QVariant argument.
   *
   * @return mpv error code (<0 on error, >= 0 on success)
   */
  static inline int set_property(mpv_handle *ctx, const QString &name,
                                 const QVariant &v)
  {
      node_builder node(v);
      return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
  }
  
  /**
   * mpv_command_node() equivalent.
   *
   * @param args command arguments, with args[0] being the command name as string
   * @return the property value, or an ErrorReturn with the error code
   */
  static inline QVariant command(mpv_handle *ctx, const QVariant &args)
  {
      node_builder node(args);
      mpv_node res;
      int err = mpv_command_node(ctx, node.node(), &res);
      if (err < 0) {
          return QVariant::fromValue(ErrorReturn(err));
      }
      node_autofree f(&res);
      return node_to_variant(&res);
  }
  
  }
  
  Q_DECLARE_METATYPE(qtmpv::ErrorReturn)
  
  #endif // MPVTOOL_H
  