[DllImport ("gstreamersharpglue-0.10.dll") ]
static extern uint gst__controllersharp_gst__controller_controlsource_get_get_value_offset ();

static uint get_value_offset = gst__controllersharp_gst__controller_controlsource_get_get_value_offset ();

[StructLayout (LayoutKind.Sequential) ]
struct GstValueArray {
  public IntPtr property_name;
  public int nbsamples;
  public ulong sample_interval;
  public IntPtr values;
}

[StructLayout (LayoutKind.Sequential) ]
struct GstControlSourceCallbacks {
  public GetValueCallbackNative get_value;
  public GetValueArrayCallbackNative get_value_array;
}

delegate bool GetValueCallbackNative (IntPtr raw, ulong timestamp, ref Gst.GLib.Value val);
delegate bool GetValueArrayCallbackNative (IntPtr raw, ulong timestamp, ref GstValueArray va);

public delegate bool GetValueCallback (ulong timestamp, ref Gst.GLib.Value value);
public delegate System.Array GetValueArrayCallback (ulong timestamp, int nsamples, ulong interval);

private GetValueCallbackWrapper GetValue_cb_wrapper;
private GetValueArrayCallbackWrapper GetValueArray_cb_wrapper;

private class GetValueCallbackWrapper {
  public bool NativeCallback (IntPtr raw, ulong timestamp, ref Gst.GLib.Value val) {
    try {
      bool __ret = managed (timestamp, ref val);

      return __ret;
    } catch (Exception e) {
      Gst.GLib.ExceptionManager.RaiseUnhandledException (e, true);
      // NOTREACHED: Above call does not return.
      throw e;
    }
  }

  internal GetValueCallbackNative NativeDelegate;
  GetValueCallback managed;

  public GetValueCallbackWrapper (GetValueCallback managed) {
    this.managed = managed;
    if (managed != null)
      NativeDelegate = new GetValueCallbackNative (NativeCallback);
  }

  public static GetValueCallback GetManagedDelegate (GetValueCallbackNative native) {
    if (native == null)
      return null;
    GetValueCallbackWrapper wrapper = (GetValueCallbackWrapper) native.Target;
    if (wrapper == null)
      return null;
    return wrapper.managed;
  }
}

private class GetValueArrayCallbackWrapper {
  public bool NativeCallback (IntPtr raw, ulong timestamp, ref GstValueArray va) {
    try {
      System.Array values = managed (timestamp, va.nbsamples, va.sample_interval);
      if (values == null)
        return false;

      System.Type t = values.GetType ();
      if (t == typeof (string[])) {
        string[] ret = (string[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteIntPtr (va.values, i * IntPtr.Size, Gst.GLib.Marshaller.StringToPtrGStrdup (ret[i]));
        }
      } else if (t == typeof (short[])) {
        short[] ret = (short[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt16 (va.values, i * 2, ret[i]);
        }
      } else if (t == typeof (ushort[])) {
        ushort[] ret = (ushort[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt16 (va.values, i * 2, (short) ret[i]);
        }
      } else if (t == typeof (int[])) {
        int[] ret = (int[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt32 (va.values, i * 4, ret[i]);
        }
      } else if (t == typeof (uint[])) {
        uint[] ret = (uint[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt32 (va.values, i * 4, (int) ret[i]);
        }
      } else if (t == typeof (long[])) {
        long[] ret = (long[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt64 (va.values, i * 8, ret[i]);
        }
      } else if (t == typeof (ulong[])) {
        ulong[] ret = (ulong[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt64 (va.values, i * 8, (long) ret[i]);
        }
      } else if (t == typeof (float[])) {
        float[] ret = (float[]) values;
        Marshal.Copy (ret, 0, va.values, va.nbsamples);
      } else if (t == typeof (double[])) {
        double[] ret = (double[]) values;
        Marshal.Copy (ret, 0, va.values, va.nbsamples);
      } else if (t == typeof (bool[])) {
        bool[] ret = (bool[]) values;

        for (int i = 0; i < va.nbsamples; i++) {
          Marshal.WriteInt32 (va.values, i * 4, ret[i] == false ? 0 : 1);
        }
      }

      return true;
    } catch (Exception e) {
      Gst.GLib.ExceptionManager.RaiseUnhandledException (e, true);
      // NOTREACHED: Above call does not return.
      throw e;
    }
  }

  internal GetValueArrayCallbackNative NativeDelegate;
  GetValueArrayCallback managed;

  public GetValueArrayCallbackWrapper (GetValueArrayCallback managed) {
    this.managed = managed;
    if (managed != null)
      NativeDelegate = new GetValueArrayCallbackNative (NativeCallback);
  }

  public static GetValueArrayCallback GetManagedDelegate (GetValueArrayCallbackNative native) {
    if (native == null)
      return null;
    GetValueArrayCallbackWrapper wrapper = (GetValueArrayCallbackWrapper) native.Target;
    if (wrapper == null)
      return null;
    return wrapper.managed;
  }
}

public void SetCallbacks (GetValueCallback get_value, GetValueArrayCallback get_value_array) {
  IntPtr off = new IntPtr (Handle.ToInt64 () + get_value_offset);

  GstControlSourceCallbacks cbs = (GstControlSourceCallbacks) Marshal.PtrToStructure (new IntPtr (Handle.ToInt64 () + get_value_offset), typeof (GstControlSourceCallbacks));

  GetValueCallbackWrapper gv_wr = new GetValueCallbackWrapper (get_value);
  GetValueArrayCallbackWrapper gva_wr = new GetValueArrayCallbackWrapper (get_value_array);

  GetValue_cb_wrapper = gv_wr;
  GetValueArray_cb_wrapper = gva_wr;

  cbs.get_value = gv_wr.NativeCallback;
  cbs.get_value_array = gva_wr.NativeCallback;

  Marshal.StructureToPtr (cbs, off, false);
}

[DllImport ("gstreamersharpglue-0.10.dll") ]
static extern bool gst__controllersharp_gst__controller_controlsource_base_bind (IntPtr handle, IntPtr pspec);

[DllImport ("gstreamersharpglue-0.10.dll") ]
static extern void gst__controllersharp_gst__controller_controlsource_override_bind (IntPtr gtype, BindNativeDelegate cb);

[Gst.GLib.CDeclCallback]
delegate bool BindNativeDelegate (IntPtr handler, IntPtr pspec);

static BindNativeDelegate Bind_cb_delegate;

static BindNativeDelegate BindVMCallback {
  get {
    if (Bind_cb_delegate == null)
      Bind_cb_delegate = new BindNativeDelegate (Bind_cb);
    return Bind_cb_delegate;
  }
}

static void OverrideBind (Gst.GLib.GType gtype) {
  OverrideBind (gtype, BindVMCallback);
}

static void OverrideBind (Gst.GLib.GType gtype, BindNativeDelegate callback) {
  gst__controllersharp_gst__controller_controlsource_override_bind (gtype.Val, callback);
}

static bool Bind_cb (IntPtr inst, IntPtr pspec) {
  try {
    ControlSource __obj = Gst.GLib.Object.GetObject (inst, false) as ControlSource;
    Gst.PropertyInfo pinfo = new Gst.PropertyInfo (pspec);
    return __obj.OnBind (pinfo);
  } catch (Exception e) {
    Gst.GLib.ExceptionManager.RaiseUnhandledException (e, false);
    return false;
  }
}

[DllImport ("libgobject-2.0-0.dll") ]
static extern IntPtr g_object_class_find_property (IntPtr klass, IntPtr property);

[Gst.GLib.DefaultSignalHandler (Type=typeof (Gst.Controller.ControlSource), ConnectionMethod="OverrideBind") ]
protected virtual bool OnBind (Gst.PropertyInfo pinfo) {
  IntPtr klass = Marshal.ReadIntPtr (Handle);
  IntPtr native_property = Gst.GLib.Marshaller.StringToPtrGStrdup (pinfo.Name);
  IntPtr pspec = g_object_class_find_property (klass, native_property);
  Gst.GLib.Marshaller.Free (native_property);

  if (pspec == IntPtr.Zero)
    return false;

  return gst__controllersharp_gst__controller_controlsource_base_bind (this.Handle, pspec);
}

[DllImport ("libgstcontroller-0.10.dll") ]
static extern bool gst_control_source_get_value_array (IntPtr raw, ulong timestamp, ref GstValueArray value_array);

[DllImport ("libglib-2.0-0.dll") ]
static extern IntPtr g_try_malloc (int size);

static readonly Type[] supported_types = new Type[] {
  typeof (string),
  typeof (short),
  typeof (ushort),
  typeof (int),
  typeof (uint),
  typeof (long),
  typeof (ulong),
  typeof (float),
  typeof (double),
  typeof (bool)
};

public System.Array GetValueArray (ulong timestamp, int nsamples, ulong interval) {
  GstValueArray va = new GstValueArray ();
  Gst.GLib.Value v = Gst.GLib.Value.Empty;

  if (!GetValue (0, ref v))
    return null;

  System.Type t = v.Val.GetType ();
  v.Dispose ();

  bool supported = false;
  foreach (System.Type tmp in supported_types)
    if (tmp == t)
      supported = true;
  if (!supported)
    throw new Exception ("Unsupported type '" + t + "'");

  int eltsize = Marshal.SizeOf (t);
  va.values = g_try_malloc (eltsize * nsamples);
  if (va.values == IntPtr.Zero)
    throw new OutOfMemoryException ();

  va.nbsamples = nsamples;
  va.sample_interval = interval;

  bool raw_ret = gst_control_source_get_value_array (Handle, timestamp, ref va);

  if (!raw_ret) {
    Gst.GLib.Marshaller.Free (va.values);
    return null;
  }

  System.Array values = Array.CreateInstance (t, nsamples);

  if (t == typeof (string)) {
    string[] ret = (string[]) values;

    for (int i = 0; i < nsamples; i++) {
      IntPtr str = Marshal.ReadIntPtr (va.values, i * IntPtr.Size);
      ret[i] = Gst.GLib.Marshaller.PtrToStringGFree (str);
    }
  } else if (t == typeof (short)) {
    short[] ret = (short[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = Marshal.ReadInt16 (va.values, i * 2);
    }
  } else if (t == typeof (ushort)) {
    ushort[] ret = (ushort[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = (ushort) Marshal.ReadInt16 (va.values, i * 2);
    }
  } else if (t == typeof (int)) {
    int[] ret = (int[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = Marshal.ReadInt32 (va.values, i * 4);
    }
  } else if (t == typeof (uint)) {
    uint[] ret = (uint[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = (uint) Marshal.ReadInt32 (va.values, i * 4);
    }
  } else if (t == typeof (long)) {
    long[] ret = (long[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = Marshal.ReadInt64 (va.values, i * 8);
    }
  } else if (t == typeof (ulong)) {
    ulong[] ret = (ulong[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = (ulong) Marshal.ReadInt64 (va.values, i * 8);
    }
  } else if (t == typeof (float)) {
    float[] ret = (float[]) values;
    Marshal.Copy (va.values, ret, 0, nsamples);
  } else if (t == typeof (double)) {
    double[] ret = (double[]) values;
    Marshal.Copy (va.values, ret, 0, nsamples);
  } else if (t == typeof (bool)) {
    bool[] ret = (bool[]) values;

    for (int i = 0; i < nsamples; i++) {
      ret[i] = Marshal.ReadInt32 (va.values, i * 4) != 0;
    }
  }

  Gst.GLib.Marshaller.Free (va.values);

  return values;
}