/*
 * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
 *
 * This file is part of Ufo.
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include "ufo-cl.h"
#include "ufo-resources.h"
#include "ufo-gpu-node.h"
#include "ufo-priv.h"

G_DEFINE_TYPE (UfoGpuNode, ufo_gpu_node, UFO_TYPE_NODE)

#define UFO_GPU_NODE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_GPU_NODE, UfoGpuNodePrivate))


struct _UfoGpuNodePrivate {
    cl_context context;
    cl_device_id device;
    cl_command_queue cmd_queue;
};

UfoNode *
ufo_gpu_node_new (gpointer context, gpointer device)
{
    UfoGpuNode *node;
    cl_int errcode;
    cl_command_queue_properties queue_properties;

    g_return_val_if_fail (context != NULL && device != NULL, NULL);

    queue_properties = CL_QUEUE_PROFILING_ENABLE;

    node = UFO_GPU_NODE (g_object_new (UFO_TYPE_GPU_NODE, NULL));
    node->priv->context = context;
    node->priv->device = device;
    node->priv->cmd_queue = clCreateCommandQueue (context, device, queue_properties, &errcode);

    UFO_RESOURCES_CHECK_CLERR (errcode);
    UFO_RESOURCES_CHECK_CLERR (clRetainContext (context));

    return UFO_NODE (node);
}

/**
 * ufo_gpu_node_get_cmd_queue:
 * @node: A #UfoGpuNode
 *
 * Get command queue associated with @node.
 *
 * Returns: (transfer none): A cl_command_queue object for @node.
 */
gpointer
ufo_gpu_node_get_cmd_queue (UfoGpuNode *node)
{
    g_return_val_if_fail (UFO_IS_GPU_NODE (node), NULL);
    return node->priv->cmd_queue;
}

/**
 * ufo_gpu_node_get_info:
 * @node: A #UfoGpuNodeInfo
 * @info: Information to be queried
 *
 * Return information about the associated OpenCL device.
 *
 * Returns: (transfer full): Information about @info.
 */
GValue *
ufo_gpu_node_get_info (UfoGpuNode *node,
                       UfoGpuNodeInfo info)
{
    UfoGpuNodePrivate *priv;
    GValue *value;

    priv = UFO_GPU_NODE_GET_PRIVATE (node);
    value = g_new0 (GValue, 1);
    memset (value, 0, sizeof (GValue));

#define READ_ULONG(d) \
    { \
        cl_ulong ulong_value; \
        UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->device, d, sizeof (cl_ulong), &ulong_value, NULL)); \
        g_value_init (value, G_TYPE_ULONG); \
        g_value_set_ulong (value, ulong_value); \
    }

    switch (info) {
        case UFO_GPU_NODE_INFO_GLOBAL_MEM_SIZE:
            READ_ULONG (CL_DEVICE_GLOBAL_MEM_SIZE);
            break;

        case UFO_GPU_NODE_INFO_MAX_MEM_ALLOC_SIZE:
            READ_ULONG (CL_DEVICE_MAX_MEM_ALLOC_SIZE);
            break;

        case UFO_GPU_NODE_INFO_LOCAL_MEM_SIZE:
            READ_ULONG (CL_DEVICE_LOCAL_MEM_SIZE);
            break;

        case UFO_GPU_NODE_INFO_MAX_WORK_GROUP_SIZE:
            {
                size_t size;

                UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof (size_t), &size, NULL));
                g_value_init (value, G_TYPE_ULONG);
                g_value_set_ulong (value, (cl_ulong) size);
            }
            break;

        case UFO_GPU_NODE_INFO_NAME:
            {
                size_t size;
                gchar *name;

                UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->device, CL_DEVICE_NAME, 0, NULL, &size));
                name = g_malloc0 (size);
                UFO_RESOURCES_CHECK_CLERR (clGetDeviceInfo (priv->device, CL_DEVICE_NAME, size, name, NULL));

                name = ufo_escape_device_name (name);
                g_value_init (value, G_TYPE_STRING);
                g_value_take_string (value, name);
            }
    }

    return value;
}

static UfoNode *
ufo_gpu_node_copy_real (UfoNode *node,
                        GError **error)
{
    UfoGpuNode *orig;

    orig = UFO_GPU_NODE (node);
    return ufo_gpu_node_new (orig->priv->context, orig->priv->device);
}

static gboolean
ufo_gpu_node_equal_real (UfoNode *n1,
                         UfoNode *n2)
{
    g_return_val_if_fail (UFO_IS_GPU_NODE (n1) && UFO_IS_GPU_NODE (n2), FALSE);
    return UFO_GPU_NODE (n1)->priv->cmd_queue == UFO_GPU_NODE (n2)->priv->cmd_queue;
}

static void
ufo_gpu_node_finalize (GObject *object)
{
    UfoGpuNodePrivate *priv;

    priv = UFO_GPU_NODE_GET_PRIVATE (object);

    if (priv->cmd_queue != NULL) {
        g_debug ("FREE cmd_queue=%p", (gpointer) priv->cmd_queue);
        UFO_RESOURCES_CHECK_CLERR (clReleaseCommandQueue (priv->cmd_queue));
        priv->cmd_queue = NULL;

        UFO_RESOURCES_CHECK_CLERR (clReleaseContext (priv->context));
    }

    G_OBJECT_CLASS (ufo_gpu_node_parent_class)->finalize (object);
}

static void
ufo_gpu_node_class_init (UfoGpuNodeClass *klass)
{
    GObjectClass *oclass = G_OBJECT_CLASS (klass);
    UfoNodeClass *node_class = UFO_NODE_CLASS (klass);

    oclass->finalize = ufo_gpu_node_finalize;
    node_class->copy = ufo_gpu_node_copy_real;
    node_class->equal = ufo_gpu_node_equal_real;

    g_type_class_add_private (klass, sizeof (UfoGpuNodePrivate));
}

static void
ufo_gpu_node_init (UfoGpuNode *self)
{
    UfoGpuNodePrivate *priv;
    self->priv = priv = UFO_GPU_NODE_GET_PRIVATE (self);
    priv->cmd_queue = NULL;
}
