# -*- coding: utf-8 -*-
# pylint: disable=too-many-lines
"""Wrapper for HAPI's APIs.
Author  : Cheneyshen
Email   : cheneyshen@tencent.com
"""
import ctypes
from ctypes import cdll, POINTER, c_int, c_int32, c_uint32, c_int64, c_uint64, \
    c_float, c_double, c_bool, c_char, byref, c_void_p, c_char_p, c_wchar_p, create_string_buffer, Array
import asyncio
from datetime import datetime
import platform
import logging

import numpy as np
import numpy.typing as npt
from typing import List, Tuple, Any
import psutil

from . import hdata as HDATA, HeightFieldSampling

HAPI_LIB = None


def is_session_valid(session):
    """Wrapper for HAPI_IsSessionValid

    Checks whether the session identified by HAPI_Session::id \
        is a valid session opened in the implementation \
            identified by HAPI_Session::type.

    Args:
        session (Session): session id

    Returns:
        bool: if the session is valid
    """
    result = HAPI_LIB.HAPI_IsSessionValid(byref(session))
    return result == HDATA.Result.SUCCESS


def cleanup(session: HDATA.Session):
    """Wrapper for HAPI_Cleanup

    Clean up memory. \
        This will unload all assets and you will need to call HAPI_Initialize() \
            again to be able to use any HAPI methods again.

    Args:
        session (Session): session id
    """
    result = HAPI_LIB.HAPI_Cleanup(byref(session))
    assert result == HDATA.Result.SUCCESS, \
        "Cleanup Failed with {0}".format(HDATA.Result(result).name)


def shutdown(session: HDATA.Session):
    """Wrapper for HAPI_Shutdown

     When using an in-process session, this method **must** be called in \
         order for the host process to shutdown cleanly. This method should \
         be called before ::HAPI_CloseSession().

    Args:
        session (Session): session of houdini you are interacting with
    """
    result = HAPI_LIB.HAPI_Shutdown(byref(session))
    assert HDATA.Result.SUCCESS == result, \
        "Shutup Failed with {0}".format(HDATA.Result(result).name)


def close_session(session: HDATA.Session):
    """Wrapper for HAPI_CloseSession

    Closes a session. If the session has been established using RPC, \
        then the RPC connection is closed.

    Args:
        session (Session): the HAPI_Session
    """
    result = HAPI_LIB.HAPI_CloseSession(byref(session))
    assert result == HDATA.Result.SUCCESS, \
        "Close Session Failed with {0}".format(HDATA.Result(result).name)


def start_thrift_named_pipe_server(
        server_options: HDATA.ThriftServerOptions,
        pipe_name: str,
        log_file: str = None) -> int:
    """Wrapper for HAPI_StartThriftNamedPipeServer

    Starts a Thrift RPC server process on the local host serving clients \
    on a Windows named pipe or a Unix domain socket and waits for it to start serving. \
        It is safe to create an RPC session using the specified pipe or \
            socket after this call succeeds.

    Args:
        server_options (ThriftServerOptions): server's option for start
        pipe_name (str) : name of pipe
        log_file (str) : if provided, logs will be appended to this file. \
            Or logs will be directed to the standard streams

    Returns:
        int: process id of server
    """
    processid = c_int32()
    result = HAPI_LIB.HAPI_StartThriftNamedPipeServer(
        byref(server_options), c_char_p(pipe_name.encode('utf-8')), byref(processid),
        c_char_p(log_file.encode('utf-8')) if log_file else None)

    assert result == HDATA.Result.SUCCESS, "StartThriftNamedPipeServer Failed with {0}".format(
        HDATA.Result(result).name)
    logging.info("Session Created with Process Id: {0}".format(processid.value))
    return processid.value


def create_thrift_named_pipe_session(session: HDATA.Session,
                                     pipe_name: str):
    """Wrapper for HAPI_CreateThriftNamedPipeSession
    Creates a Thrift RPC session using a Windows named 、
    pipe or a Unix domain socket as transport.

    Args:
        session (Session): session id
        pipe_name (str) : the name of the pipe or socket
    """
    result = HAPI_LIB.HAPI_CreateThriftNamedPipeSession(byref(session), c_char_p(pipe_name.encode('utf-8')))
    assert result == HDATA.Result.SUCCESS, "CreateThriftNamedPipeSession Failed with {0}".format(
        HDATA.Result(result).name)


def initialize(session: HDATA.Session,
               cook_option: HDATA.CookOptions,
               use_cooking_thread: bool = True,  # pylint: disable=too-many-arguments
               cooking_thread_stack_size: int = -1,
               houdini_environment_files: str = "",
               otl_search_path: str = "",
               dso_search_path: str = "",
               image_dso_search_path: str = "",
               audio_dso_search_path: str = ""):
    """Wrapper for HAPI_Initialize

    Create the asset manager, set up environment variables, \
        and initialize the main Houdini scene. No license checking is during this step. \
            Only when you try to load an asset library (OTL) do we actually check for licenses.

    Args:
        session (Session): session id
        cook_option (CookOption): option for node cook
        use_cooking_thread (bool, optional): Use a separate thread for cooking of assets.\
             This allows for asynchronous cooking and larger stack size.. Defaults to True.
        cooking_thread_stack_size (int, optional): Set the stack size of the cooking thread.\
             Use -1 to set the stack size to the Houdini default. This value is in bytes. \
                 Defaults to -1.
        houdini_environment_files (str, optional): A list of paths,\
             separated by a ";" on Windows and a ":" on Linux and Mac,\
                  to .env files that follow the same syntax as the houdini.env file in Houdini's \
                      user prefs folder.These will be applied after the default houdini.env file \
                          and will overwrite the process' environment variable values. You an use \
                              this to enforce a stricter environment when running engine. \
                                  For more info, see: \
                                      http://www.sidefx.com/docs/houdini/basics/config_env. \
                                          Defaults to "".
        otl_search_path (str, optional): The directory where OTLs are searched for. \
            You can pass NULL here which will only use the default Houdini OTL search paths. \
                You can also pass in multiple paths separated by a ";" on Windows and a ":" \
                    on Linux and Mac. \
                    If something other than NULL is passed the default Houdini search paths \
                        will be appended to the end of the path string.. Defaults to "".
        dso_search_path (str, optional): The directory where generic DSOs (custom plugins) \
            are searched for. You can pass NULL here which will only use the default Houdini \
                DSO search paths. \
                You can also pass in multiple paths separated by a ";" on Windows and a ":" \
                    on Linux and Mac. \
                    If something other than NULL is passed the default Houdini search paths \
                        will be appended to the end of the path string.. Defaults to "".
        image_dso_search_path (str, optional): The directory where image DSOs (custom plugins) \
            are searched for. \
            You can pass NULL here which will only use the default Houdini DSO search paths. \
                You can also pass in multiple paths separated by a ";" on Windows and a ":" \
                    on Linux and Mac. If something other than NULL is passed the default Houdini \
                        search paths will be appended to the end of the path string.. \
                            Defaults to "".
        audio_dso_search_path (str, optional): The directory where audio DSOs (custom plugins)\
            are searched for. You can pass NULL here which will only use the default Houdini \
                DSO search paths. You can also pass in multiple paths separated by a ";" \
                    on Windows and a ":" on Linux and Mac. If something other than NULL \
                        is passed the default Houdini search paths will be appended to \
                            the end of the path string.. Defaults to "".
    """
    result = HAPI_LIB.HAPI_Initialize(
        byref(session),
        byref(cook_option),
        c_bool(use_cooking_thread),
        c_int(cooking_thread_stack_size),
        # None, None, None, None, None)

        c_char_p(houdini_environment_files.encode('utf-8')),
        c_char_p(otl_search_path.encode('utf-8')),
        c_char_p(dso_search_path.encode('utf-8')),
        c_char_p(image_dso_search_path.encode('utf-8')),
        c_char_p(audio_dso_search_path.encode('utf-8')))
    assert result in (HDATA.Result.SUCCESS, HDATA.Result.ALREADY_INITIALIZED), \
        "Initialize Failed with {0}".format(HDATA.Result(result).name)


def load_asset_library_from_file(session: HDATA.Session,
                                 file_path: str,
                                 allow_overwrite: bool = True) -> int:
    """Wrapper for HAPI_LoadAssetLibraryFromFile

    Loads a Houdini asset library (OTL) from a .otl file. \
        It does NOT create anything inside the Houdini scene.

    Args:
        session (Session): The session of Houdini you are interacting with.
        file_path (str): Absolute path to the .otl file.
        allow_overwrite (bool, optional): With this true, if the library file being \
            loaded contains asset definitions that have already been loaded they will \
                overwrite the existing definitions. . Defaults to True.

    Returns:
        int: Newly loaded otl id to be used
    """
    asset_lib_id = c_int32()
    result = HAPI_LIB.HAPI_LoadAssetLibraryFromFile(
        byref(session), c_char_p(file_path.encode('utf-8')),
        c_bool(allow_overwrite), byref(asset_lib_id))
    assert result == HDATA.Result.SUCCESS, \
        "LoadAssetLibraryFromFile Failed with {0}".format(
            HDATA.Result(result).name)
    return asset_lib_id.value


def get_asset_definition_parm_counts(session: HDATA.Session,
                                     library_id: int,
                                     asset_name: str) \
        -> Tuple[int, int, int, int, int]:
    """Wrapper for HAPI_GetAssetDefinitionParmCounts

    Get the number of asset parameters contained in an asset library, \
        as well as the number of parameter int, float, string, and choice values.

    Args:
        session (Session): The session of Houdini you are interacting with.
        library_id (int): Library ID of asset
        asset_name (str): Name of the asset to get the parm counts for.

    Returns:
        parm_count (int) : The number of parameters in the asset library.
    	int_value_count (int) : The number of int values for parameters in the asset library.
    	float_value_count (int) : The number of float values for parameters in the asset library.
    	string_value_count (int) : The number of string values for parameters in the asset library.
    	choice_value_count (int) : The number of choice values for parameters in the asset library.
    """
    parm_count = c_int32()
    int_value_count = c_int32()
    float_value_count = c_int32()
    string_value_count = c_int32()
    choice_value_count = c_int32()

    result = HAPI_LIB.HAPI_GetAssetDefinitionParmCounts(
        byref(session),
        library_id,
        c_char_p(asset_name.encode("utf-8")),
        byref(parm_count), byref(int_value_count), byref(float_value_count), byref(string_value_count),
        byref(choice_value_count))
    assert result == HDATA.Result.SUCCESS, \
        "GetAssetDefinitionParmCounts Failed with {0}".format(
            HDATA.Result(result).name)

    return parm_count.value, int_value_count.value, float_value_count.value, \
           string_value_count.value, choice_value_count.value


def get_asset_definition_parm_infos(session: HDATA.Session,
                                    library_id: int,
                                    asset_name: str):
    """Wrapper for HAPI_GetAssetDefinitionParmInfos

    Fill an array of HAPI_ParmInfo structs with parameter information \
        for the specified asset in the specified asset library.

    Args:
        session (Session): The session of Houdini you are interacting with.
        library_id (int): Library ID of asset
        asset_name (str): Name of the asset to get the parm counts for.

    Returns:
        list(ParmInfo) : Array of HAPI_ParmInfo at least the size of length.
    """
    parm_count, _, _, _, _ = get_asset_definition_parm_counts(session, library_id, asset_name)

    parm_info_buf = (HDATA.ParmInfo * parm_count)()

    result = HAPI_LIB.HAPI_GetAssetDefinitionParmInfos(
        byref(session),
        library_id,
        c_char_p(asset_name.encode("utf-8")),
        byref(parm_info_buf),
        0, parm_count)

    assert result == HDATA.Result.SUCCESS, \
        "GetAssetDefinitionParmInfos Failed with {0}".format(
            HDATA.Result(result).name)

    return parm_info_buf


def get_asset_definition_parm_values(session: HDATA.Session,
                                     library_id: int,
                                     asset_name: str,
                                     string_evaluate: bool = False):
    """Wrapper for HAPI_GetAssetDefinitionParmValues

    Fill arrays of parameter int values, float values, string values, \
        and choice values for parameters in the specified asset in the specified asset library.

    Args:
        session (Session): The session of Houdini you are interacting with.
        library_id (int): Library ID of asset
        asset_name (str): Name of the asset to get the parm counts for.
        string_evaluate(str): Whether or not to evaluate the string expressions. 

    Returns:
    """
    pc, ic, fc, sc, cc = get_asset_definition_parm_counts(session, library_id, asset_name)

    parm_info_buf = (HDATA.ParmInfo * pc)()

    result = HAPI_LIB.HAPI_GetAssetDefinitionParmInfos(
        byref(session),
        library_id,
        c_char_p(asset_name.encode("utf-8")),
        byref(parm_info_buf),
        0, pc)

    assert result == HDATA.Result.SUCCESS, \
        "get_asset_definition_parm_values Failed with {0}".format(
            HDATA.Result(result).name)

    int_buf = (c_int32 * ic)()
    float_buf = (c_float * fc)()
    sh_buf = (c_int32 * sc)()
    choice_buf = (HDATA.ParmChoiceInfo * cc)()

    print(library_id, asset_name)
    print("get values\n\n")

    result = HAPI_LIB.HAPI_GetAssetDefinitionParmValues(
        byref(session), library_id, c_char_p(asset_name.encode("utf-8")),
        byref(int_buf), 0, ic,
        byref(float_buf), 0, fc,
        c_bool(string_evaluate),
        byref(sh_buf), 0, sc,
        byref(choice_buf), 0, cc)
    # TODO: get this working    
    assert result == HDATA.Result.SUCCESS, \
        "HAPI_GetAssetDefinitionParmValues Failed with {0}".format(
            HDATA.Result(result).name)


def get_available_asset_count(session: HDATA.Session,
                              asset_lib_id: int) -> int:
    """Wrapper for HAPI_GetAvailableAssetCount

    Get the number of assets contained in an asset library. \
        You should call HAPI_LoadAssetLibraryFromFile() prior to get a library_id.

    Args:
        session (Session): The session of Houdini you are interacting with.
        asset_lib_id (int): Library ID of asset

    Returns:
        int : asset_count 
    """
    asset_count = c_int32()
    result = HAPI_LIB.HAPI_GetAvailableAssetCount(
        byref(session), asset_lib_id, byref(asset_count))
    assert result == HDATA.Result.SUCCESS, \
        "GetAvailableAssetCount Failed with {0}".format(
            HDATA.Result(result).name)
    return asset_count.value


def get_available_assets(session: HDATA.Session,
                         asset_lib_id: int) -> List[str]:
    """Get the names of the assets contained in an asset library.
    The asset names will contain additional information about the type of asset, \
        namespace, and version, along with the actual asset name. \
            For example, if you have an Object type asset, in the "hapi" namespace, \
                of version 2.0, named "foo", the asset name returned here \
                    will be: hapi::Object/foo::2.0

    Args:
        session (Session): The session of Houdini you are interacting with.
        asset_lib_id (int): Newly loaded otl id to be used

    Returns:
        list(str): names of available assets name
    """
    asset_count = get_available_asset_count(session, asset_lib_id)

    asset_string_buffer = (c_int32 * asset_count)()

    result = HAPI_LIB.HAPI_GetAvailableAssets(
        byref(session), asset_lib_id, byref(asset_string_buffer), asset_count)
    assert result == HDATA.Result.SUCCESS, \
        "GetAvailableAssets Failed with {0}".format(
            HDATA.Result(result).name)

    asset_names = []
    for buffer in asset_string_buffer:
        asset_name = get_string(session, buffer)
        asset_names.append(asset_name)

    return asset_names


def create_input_node(session: HDATA.Session,
                      node_label: str) -> int:
    """Wrapper for HAPI_CreateInputNode
    Creates a simple geometry SOP node that can accept geometry input.
    This will create a dummy OBJ node with a Null SOP inside that you can set
    the geometry of using the geometry SET APIs.
    You can then connect this node to any other node as a geometry input.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_label ( str): Give this input node a name for easy debugging. \
            The node's parent OBJ node and the Null SOP node will both get \
                this given name with "input_" prepended. You can also pass \
                    NULL in which case the name will be "input#" where # is \
                    some number.

    Returns:
        int : Newly created node's id
    """
    node_id = c_int32()
    result = HAPI_LIB.HAPI_CreateInputNode(
        byref(session), byref(node_id), c_char_p(node_label.encode('utf-8')))
    assert result == HDATA.Result.SUCCESS, \
        "CreateInputNode Failed with {0}".format(
            HDATA.Result(result).name)
    return node_id.value


def create_input_curve_node(session: HDATA.Session,
                            node_label: str) -> int:
    """Wrapper for HAPI_CreateInputCurveNode
    Helper for creating specifically creating a curve input geometry SOP.
            This will create a dummy OBJ node with a Null SOP inside that
            contains the the HAPI_ATTRIB_INPUT_CURVE_COORDS attribute.
            It will setup the node as a curve part with no points.
            In addition to creating the input node, it will also commit and cook
            the geometry.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_label ( str): Give this input node a name for easy debugging. \
            The node's parent OBJ node and the Null SOP node will both get \
                this given name with "input_" prepended. You can also pass \
                    NULL in which case the name will be "input#" where # is \
                    some number.

    Returns:
        int : Newly created node's id
    """
    node_id = c_int32()
    result = HAPI_LIB.HAPI_CreateInputCurveNode(
        byref(session), byref(node_id), c_char_p(node_label.encode('utf-8')))
    assert result == HDATA.Result.SUCCESS, \
        "CreateInputCurveNode Failed with {0}".format(
            HDATA.Result(result).name)
    return node_id.value


def create_heightfield_input_node(session: HDATA.Session,
                                  node_label: str,
                                  xsize: int,
                                  ysize: int,
                                  voxel_size: int) \
        -> Tuple[int, int, int, int]:
    """Wrapper for HAPI_CreateHeightfieldInputNode

    Creates the required node hierarchy needed for Heightfield inputs.

    note this uses center sampling. use create_height_field_input() \
    to specify corner sampling

    @deprecated use create_height_field_input() instead

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_label ( str): Give this input node a name for easy debugging. \
            The node's parent OBJ node and the Null SOP node will both get \
                this given name with "input_" prepended. You can also pass \
                    NULL in which case the name will be "input#" where # is \
                    some number.
        xsize (int): size of the heightfield in X
        ysize (int): size of the heightfield in y
        voxel_size (int): Size of the voxel

    Returns:
        int[4] : Newly created id for Heightfield node, for height volume\
            for mask volume and for merge node
    """
    heightfield_id = c_int32()
    height_id = c_int32()
    mask_id = c_int32()
    merge_id = c_int32()
    # result = HAPI_LIB.HAPI_CreateHeightFieldInput(
    result = HAPI_LIB.HAPI_CreateHeightFieldInput(
        byref(session), c_int(-1), c_char_p(node_label.encode('utf-8')),
        c_int(xsize), c_int(ysize), c_float(voxel_size), HDATA.HeightFieldSampling.HAPI_HEIGHTFIELD_SAMPLING_CENTER,
        byref(heightfield_id), byref(height_id), byref(mask_id), byref(merge_id))
    assert result == HDATA.Result.SUCCESS, "CreateHeightfieldInputNode Failed with {0}".format(
        HDATA.Result(result).name)
    return heightfield_id.value, height_id.value, mask_id.value, merge_id.value


def create_height_field_input(session: HDATA.Session,
                              node_label: str,
                              xsize: int,
                              ysize: int,
                              voxelsize: int,
                              sampling: HDATA.HeightFieldSampling,
                              parent_node_id: int = -1) \
        -> Tuple[int, int, int, int]:
    """Wrapper for HAPI_CreateHeightFieldInput

    Creates the required node hierarchy needed for Heightfield inputs.


    Args:
        session (Session): The session of Houdini you are interacting with.
        node_label (str): Give this input node a name for easy debugging. \
            The node's parent OBJ node and the Null SOP node will both get \
                this given name with "input_" prepended. You can also pass \
                    NULL in which case the name will be "input#" where # is \
                    some number.
        xsize (int): size of the heightfield in X
        ysize (int): size of the heightfield in y
        voxelsize (int): Size of the voxel
        sampling (HeightFieldSampling): Type of sampling which should be either center or corner.
        parent_node_id (int): The parent node network's node id or -1 if the parent \
                    network is the manager (top-level) node. In that case, \
                    the manager must be identified by the table name in the \
                    operator_name.
                    <!-- min -1 -->
                    <!-- default -1 -->

    Returns:
        int[4] : Newly created id for Heightfield node, for height volume\
            for mask volume and for merge node
    """
    heightfield_id = c_int32()
    height_id = c_int32()
    mask_id = c_int32()
    merge_id = c_int32()
    result = HAPI_LIB.HAPI_CreateHeightFieldInput(
        byref(session), parent_node_id, c_char_p(node_label.encode('utf-8')),
        c_int(xsize), c_int(ysize), c_float(voxelsize), sampling,
        byref(heightfield_id), byref(height_id), byref(mask_id), byref(merge_id))
    assert result == HDATA.Result.SUCCESS, "CreateHeightfieldInput Failed with {0}".format(
        HDATA.Result(result).name)
    return heightfield_id.value, height_id.value, mask_id.value, merge_id.value


def create_heightfield_volume_input_node(session: HDATA.Session,
                                         node_label: str,
                                         xsize: int,
                                         ysize: int,
                                         voxel_size: int,
                                         parent_node_id: int = -1) \
        -> int:
    """Wrapper for HAPI_CreateHeightfieldInputVolumeNode
    Creates a volume input node that can be used with Heightfields.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_label ( str): Give this input node a name for easy debugging. \
            The node's parent OBJ node and the Null SOP node will both get \
                this given name with "input_" prepended. You can also pass \
                    NULL in which case the name will be "input#" where # is \
                    some number.
        parent_node_id (int) :
                 The parent node network's node id or -1 if the parent
                 network is the manager (top-level) node. In that case,
                 the manager must be identified by the table name in the
        xsize (int): size of the heightfield in X
        ysize (int): size of the heightfield in y
        voxel_size (int): Size of the voxel

    Returns:
        int[4] : Newly created id for Heightfield node, for height volume\
            for mask volume and for merge node
    """
    node_id = c_int32()
    result = HAPI_LIB.HAPI_CreateHeightfieldInputVolumeNode(
        byref(session), c_int32(parent_node_id), byref(node_id), c_char_p(node_label.encode('utf-8')),
        xsize, ysize, c_float(voxel_size))
    assert result == HDATA.Result.SUCCESS, \
        "CreateHeightfieldVolumeInputNode Failed with {0}".format(
            HDATA.Result(result).name)
    return node_id.value


def create_node(session, operator_name, node_label="", parent_node_id=-1, cook_on_creation=False):
    """Wrapper for HAPI_CreateNode
    Create a node inside a node network. \
        Nodes created this way will have their HAPI_NodeInfo::createdPostAssetLoad \
            set to true.

    Args:
        session (Session): The session of Houdini you are interacting with.
        operator_name (str): The name of the node operator type.
        node_label (str, optional): The label of the newly created node. Defaults to "".
        parent_node_id (int, optional): [description]. Defaults to -1.
        cook_on_creation (bool, optional): [description]. Defaults to False.

    Returns:
        int: The returned node id of the just-created node.
    """
    node_id = c_int32()
    result = HAPI_LIB.HAPI_CreateNode(
        byref(session), c_int(parent_node_id), c_char_p(
            operator_name.encode('utf-8')),
        c_char_p(node_label.encode('utf-8')), c_bool(cook_on_creation), byref(node_id))
    assert result == HDATA.Result.SUCCESS, \
        "CreateNode Failed with {0}".format(HDATA.Result(result).name)
    return node_id.value


def delete_node(session: HDATA.Session,
                node_id: int):
    """Wrapper for HAPI_DeleteNode
    Delete a node from a node network.\
        Only nodes with their HAPI_NodeInfo::createdPostAssetLoad \
            set to true can be deleted this way.
    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to delete.
    """
    result = HAPI_LIB.HAPI_DeleteNode(byref(session), node_id)
    assert result == HDATA.Result.SUCCESS, \
        "DeleteNode Failed with {0}".format(HDATA.Result(result).name)


def cook_node(session: HDATA.Session,
              cook_option: HDATA.CookOptions,
              node_id: int,
              status_report_interval: float = 0.1,
              status_verbosity: HDATA.StatusVerbosity = HDATA.StatusVerbosity.ALL):
    """Wrapper for HAPI_CookNode, a sync/blocking call
    Initiate a cook on this node. \
        Note that this may trigger cooks on other nodes if they are connected.

    Args:
        session (Session): The session of Houdini you are interacting with.
        cook_option (CookOption): option for node cook
        node_id (int): The node to cook.
    """
    result = HAPI_LIB.HAPI_CookNode(
        byref(session), c_int(node_id), byref(cook_option))
    assert result == HDATA.Result.SUCCESS, \
        "CookNode Failed with {0}".format(HDATA.Result(result).name)
    new_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(new_loop)
    new_loop.run_until_complete(wait_cook_async(session, status_report_interval, status_verbosity))


async def cook_node_async(session: HDATA.Session,
                          cook_option: HDATA.CookOptions,
                          node_id: int,
                          status_report_interval: float = 0.1,
                          status_verbosity: HDATA.StatusVerbosity = HDATA.StatusVerbosity.ALL,
                          file_logger=None, name=None, pid=None):
    """Wrapper for HAPI_CookNode, an async call
    Initiate a cook on this node. \
        Note that this may trigger cooks on other nodes if they are connected.

    Args:
        session (Session): The session of Houdini you are interacting with.
        cook_option (CookOption): option for node cook
        node_id (int): The node to cook.
    """
    result = HAPI_LIB.HAPI_CookNode(
        byref(session), c_int(node_id), byref(cook_option))
    assert result == HDATA.Result.SUCCESS, \
        "CookNodeAsync Failed with {0}".format(HDATA.Result(result).name)
    await wait_cook_async(session, status_report_interval, status_verbosity, file_logger=file_logger, name=name, pid=pid)


def wait_cook(session: HDATA.Session,
              status_report_interval: float = 0.1,
              status_verbosity: HDATA.StatusVerbosity = HDATA.StatusVerbosity.ALL):
    """An sync call to wait for cooking return result

    Args:
        session (Session): The session of Houdini you are interacting with.
        status_report_interval (int, optional): time interval in seconds to query cook status. \
            Defaults to 1.
    """
    new_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(new_loop)
    new_loop.run_until_complete(wait_cook_async(session, status_report_interval, status_verbosity))


async def wait_cook_async(session: HDATA.Session,
                          status_report_interval: float = 0.1,
                          status_verbosity: HDATA.StatusVerbosity = HDATA.StatusVerbosity.ALL,
                          file_logger=None,
                          name=None,
                          pid=None):
    """An async call to wait for cooking return result

    Args:
        session (Session): The session of Houdini you are interacting with.
        status_report_interval (int, optional): time interval in seconds to query cook status. \
            Defaults to 0.016. kivlin.
    """
    logger = logging if file_logger is None else file_logger
    if status_verbosity > HDATA.StatusVerbosity.WARNINGS:
        logger.info("Start Cooking {0}".format(name))
    cook_status = c_int32()
    cook_result = HDATA.Result.ALREADY_INITIALIZED
    previous_status_string = ''
    import time
    previous_profile_time = time.time()
    profile_interval = 10.0
    process = psutil.Process(pid)
    while True:
        cook_result = HAPI_LIB.HAPI_GetStatus(
            byref(session), 2, byref(cook_status))
        continuestate = cook_status.value > HDATA.State.MAX_READY_STATE \
                        and cook_result == HDATA.Result.SUCCESS
        if status_verbosity > HDATA.StatusVerbosity.WARNINGS:
            status_string = _get_status_string(session,HDATA.StatusType.COOK_STATE,status_verbosity)
            if status_string != previous_status_string:
                previous_status_string = status_string
                # logger.info("Cook Status at session {0} {1}: {2}".format(session.id, datetime.now().strftime('%H:%M:%S'), status_string))
                logger.info(status_string + ('----[CPU:{0}%,MEM:{1}%]'.format(process.cpu_percent(), format(process.memory_percent(), '.3f'))))
                previous_profile_time = time.time() # kivlin. reset profile time.
            elif (previous_profile_time + profile_interval) < time.time():
                logger.info(status_string + ('----[CPU:{0}%,MEM:{1}%]'.format(process.cpu_percent(), format(process.memory_percent(), '.3f'))))
                previous_profile_time = time.time()  # kivlin. reset profile time.
        if not continuestate:
            break
        await asyncio.sleep(status_report_interval)
    if cook_status.value == HDATA.State.READY_WITH_FATAL_ERRORS:
        logger.error("Cook with Fatal Error at node {0} : {1}".format(name, _get_status_string(session)))
    if status_verbosity > HDATA.StatusVerbosity.WARNINGS:
        logger.info("Finish Cooking {0}".format(name))
    assert cook_result == HDATA.Result.SUCCESS and \
           cook_status.value == HDATA.State.READY, \
        "CookNode Failed with {0} at node {1} and Cook Status is {2}". \
            format(HDATA.Result(cook_result).name, name,
                   HDATA.State(cook_status.value).name)


def query_node_input(session: HDATA.Session,
                     node_id: int,
                     input_index: int = 0) \
        -> int:
    """Wrapper for HAPI_QueryNodeInput
    Query which node is connected to another node's input.

    Args:
        session (int): The session of Houdini you are interacting with.
        node_id (int): The node to cook.
        input_index (int, optional): [description]. Defaults to 0.

    Returns:
        int: The node id of connection
    """
    connect_node_id = c_int32()
    result = HAPI_LIB.HAPI_QueryNodeInput(
        byref(session), c_int(node_id), c_int(input_index), byref(connect_node_id))
    assert result == HDATA.Result.SUCCESS, \
        "QueryNodeInput Failed with {0}".format(HDATA.Result(result).name)
    return connect_node_id.value


def connect_node_input(session: HDATA.Session,
                       node_id: int,
                       node_id_to_connect: int,
                       input_index: int = 0,
                       output_index: int = 0):
    """Wrapper for HAPI_ConnectNodeInput
    Connect two nodes together.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to connect.
        node_id_to_connect (int): The node to connect to node_id's input.
        input_index (int, optional): The input index. Defaults to 0.
        output_index (int, optional): The output index. Defaults to 0.
    """
    result = HAPI_LIB.HAPI_ConnectNodeInput(
        byref(session), c_int(node_id), c_int(input_index),
        c_int(node_id_to_connect), c_int(output_index))
    assert result == HDATA.Result.SUCCESS, \
        "ConnectNodeInput Failed with {0}".format(
            HDATA.Result(result).name)


def disconnect_node_input(session: HDATA.Session,
                          node_id: int,
                          input_index: int = 0):
    """Wrapper for HAPI_DisconnectNodeInput
    Disconnect a node input.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to disconnect.
        input_index (int, optional): The input index to disconnect. Defaults to 0.
    """
    result = HAPI_LIB.HAPI_DisconnectNodeInput(
        byref(session), c_int(node_id), c_int(input_index))
    assert result == HDATA.Result.SUCCESS, \
        "DisconnectNodeInput Failed with {0}".format(
            HDATA.Result(result).name)


def get_composed_child_node_list(session: HDATA.Session,
                                 node_id: int,
                                 count: int) \
        -> Array:
    """Wrapper for HAPI_GetComposedChildNodeList
    Get the composed list of child node ids from the previous call to HAPI_ComposeChildNodeList().

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The parent node id.
        count (int): The number of children in the composed list

    Returns:
        list(int): The array of node id for the child nodes.
    """
    id_buffer = (c_int32 * count)()
    result = HAPI_LIB.HAPI_GetComposedChildNodeList(
        byref(session), c_int(node_id), byref(id_buffer), c_int(count))
    assert result == HDATA.Result.SUCCESS, \
        "GetComposedChildNodeList Failed with {0}".format(
            HDATA.Result(result).name)
    return id_buffer


def compose_child_node_list(session: HDATA.Session,
                            node_id: int,
                            node_type: HDATA.NodeType = HDATA.NodeType.ANY,
                            node_flag: HDATA.NodeFlags = HDATA.NodeFlags.ANY) \
        -> int:
    """Wrapper for HAPI_ComposeChildNodeList
    Compose a list of child nodes based on given filters.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The parent node id.
        node_type (NodeType, optional): The node type by which to filter the children. \
            Defaults to HDATA.NodeType.ANY.
        node_flag (NodeFlags, optional): The node flags by which to filter the children. \
            Defaults to HDATA.NodeFlags.ANY.
    """
    child_count = c_int32()
    result = HAPI_LIB.HAPI_ComposeChildNodeList(
        byref(session), c_int(node_id), c_int(node_type), c_int(node_flag),
        c_bool(False), byref(child_count))
    assert result == HDATA.Result.SUCCESS, \
        "ComposeChildNodeList Failed with {0}".format(
            HDATA.Result(result).name)
    return child_count.value


def get_display_geo_info(session: HDATA.Session,
                         node_id: int) \
        -> HDATA.GeoInfo:
    """Wrapper for HAPI_GetDisplayGeoInfo
    Get the display geo (SOP) node inside an Object node. \
        If there there are multiple display SOP nodes, only the first one is returned. \
            If the node is a display SOP itself, even if a network, \
                it will return its own geo info. If the node is a SOP \
                    but not a network and not the display SOP, this function will fail.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        GeoInfo: the geoinfo of queried node
    """
    geo_info = HDATA.GeoInfo()
    result = HAPI_LIB.HAPI_GetDisplayGeoInfo(byref(session), c_int(node_id), byref(geo_info))

    assert result == HDATA.Result.SUCCESS, "GetDisplayGeoInfo Failed with {0}".format(HDATA.Result(result).name)
    return geo_info


def get_geo_info(session: HDATA.Session,
                 node_id: int) \
        -> HDATA.GeoInfo:
    """Wrapper for HAPI_GetGeoInfo
    Get the geometry info struct (HAPI_GeoInfo) on a SOP node.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        GeoInfo: the geoinfo of queried node
    """
    geo_info = HDATA.GeoInfo()
    result = HAPI_LIB.HAPI_GetGeoInfo(
        byref(session), c_int(node_id), byref(geo_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetGeoInfo Failed with {0}".format(
            HDATA.Result(result).name)
    return geo_info


def get_part_info(session: HDATA.Session,
                  node_id: int,
                  part_id: int = 0) \
        -> HDATA.PartInfo:
    """Wrapper for HAPI_GetPartInfo
    Get a particular part info struct.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part id.

    Returns:
        PartInfo: the partinfo of queried node
    """
    part_info = HDATA.PartInfo()
    result = HAPI_LIB.HAPI_GetPartInfo(
        byref(session), c_int(node_id), c_int(part_id), byref(part_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetPartInfo Failed with {0}".format(HDATA.Result(result).name)
    return part_info


def get_instanced_part_ids(session: HDATA.Session,
                           node_id: int,
                           part_id: int) \
        -> Array:
    part_info: HDATA.PartInfo = get_part_info(session, node_id, part_id)

    id_buffer = (c_int32 * part_info.instancedPartCount)()
    result = HAPI_LIB.HAPI_GetInstancedPartIds(byref(session), c_int(node_id), c_int(part_id),
                                               byref(id_buffer), c_int(0), part_info.instancedPartCount)
    assert result == HDATA.Result.SUCCESS, "GetInstancedPartIds Failed with {0}".format(HDATA.Result(result).name)
    return id_buffer


def get_instancer_part_transforms(session: HDATA.Session,
                                  node_id: int,
                                  part_id: int,
                                  rst_order: HDATA.RSTOrder) \
        -> Array:
    part_info: HDATA.PartInfo = get_part_info(session, node_id, part_id)

    xform_buffer = (HDATA.Transform * part_info.instanceCount)()
    result = HAPI_LIB.HAPI_GetInstancerPartTransforms(byref(session), node_id, part_id,
                                                      rst_order, byref(xform_buffer), 0,
                                                      part_info.instanceCount)
    assert result == HDATA.Result.SUCCESS, "GetInstancerPartTransforms Failed with {0}".format(
        HDATA.Result(result).name)
    return xform_buffer


def set_volume_info(session: HDATA.Session,
                    node_id: int,
                    part_id: int,
                    volume_info: HDATA.VolumeInfo):
    """Wrapper for HAPI_SetVolumeInfo
    Set the volume info of a geo on a geo input.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The partid to set
        volume_info (VolumeInfo):  the volumeinfo to set
    """
    result = HAPI_LIB.HAPI_SetVolumeInfo(
        byref(session), node_id, part_id, byref(volume_info))
    assert result == HDATA.Result.SUCCESS, \
        "SetVolume Failed with {0}".format(HDATA.Result(result).name)


def get_volume_info(session: HDATA.Session,
                    node_id: int,
                    part_id: int) \
        -> HDATA.VolumeInfo:
    """Wrapper for HAPI_GetVolumeInfo
    Set the volume info of a geo on a geo input.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The partid to set

    Returns:
        VolumeInfo: the VolumeInfo of queried node
    """
    volume_info = HDATA.VolumeInfo()
    result = HAPI_LIB.HAPI_GetVolumeInfo(
        byref(session), node_id, part_id, byref(volume_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetVolumeInfo Failed with {0}".format(HDATA.Result(result).name)
    return volume_info


def get_first_volume_tile(session: HDATA.Session,
                          node_id: int,
                          part_id: int = 0) \
        -> HDATA.VolumeTileInfo:
    """Wrapper for HAPI_GetFirstVolumeTile
    Iterate through a volume based on 8x8x8 sections of the volume \
    Start iterating through the value of the volume at part_id.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part to get

    Returns:
        VolumeTileInfo: the VolumeTileInfo of queried node
    """
    volume_tile_info = HDATA.VolumeTileInfo()
    result = HAPI_LIB.HAPI_GetFirstVolumeTile(
        byref(session), c_int(node_id), c_int32(part_id), byref(volume_tile_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetFirstVolumeTile Failed with {0}".format(HDATA.Result(result).name)
    return volume_tile_info


def get_next_volume_tile(session: HDATA.Session,
                         node_id: int,
                         part_id: int = 0) \
        -> HDATA.VolumeTileInfo:
    """Wrapper for HAPI_GetNextVolumeTile
    terate through a volume based on 8x8x8 sections of the volume \
    Continue iterating through the value of the volume at part_id

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part to get

    Returns:
        VolumeTileInfo: the VolumeTileInfo of queried node
    """
    volume_tile_info = HDATA.VolumeTileInfo()
    result = HAPI_LIB.HAPI_GetNextVolumeTile(
        byref(session), c_int(node_id), c_int32(part_id), byref(volume_tile_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetFirstVolumeTile Failed with {0}".format(HDATA.Result(result).name)
    return volume_tile_info


def get_composed_object_list(session: HDATA.Session,
                             node_id: int,
                             count: int) \
        -> Array:
    """Wrapper for HAPI_GetComposedObjectList
    Fill an array of HAPI_ObjectInfo structs.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        count (int): The number of children in the list

    Returns:
        list(ObjectInfo): Array of ObjectInfo of querying
    """
    object_info_buffer = (HDATA.ObjectInfo * count)()
    result = HAPI_LIB.HAPI_GetComposedObjectList(
        byref(session), c_int(node_id), byref(object_info_buffer),
        c_int(0), c_int(count))
    assert result == HDATA.Result.SUCCESS, \
        "GetComposedObjectList Failed with {0}".format(
            HDATA.Result(result).name)
    return object_info_buffer


def compose_object_list(session: HDATA.Session,
                        node_id: int) \
        -> int:
    """Wrapper for HAPI_ComposeObjectList
    Compose a list of child object nodes given a parent node id.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        int: child count of querying node
    """
    child_count = c_int32()
    result = HAPI_LIB.HAPI_ComposeObjectList(
        byref(session), node_id, None, byref(child_count))
    assert result == HDATA.Result.SUCCESS, \
        "ComposeObjectList Failed with {0}".format(
            HDATA.Result(result).name)
    return child_count.value


def get_manager_node_id(session: HDATA.Session,
                        node_type: HDATA.NodeType) \
        -> int:
    """Wrapper for HAPI_GetManagerNodeId
    Get the root node of a particular network type (ie. OBJ).

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_type (NodeType): The node type

    Returns:
        int: id of root node
    """
    root_id = c_int32()
    result = HAPI_LIB.HAPI_GetManagerNodeId(byref(session), node_type, byref(root_id))
    assert result == HDATA.Result.SUCCESS, \
        "GetManagerNodeId Failed with {0}".format(
            HDATA.Result(result).name)
    return root_id.value


def get_node_info(session: HDATA.Session,
                  node_id: int) \
        -> HDATA.NodeInfo:
    """Wrapper for HAPI_GetNodeInfo
    Fill an NodeInfo struct.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        NodeInfo: NodeInfo of querying node
    """
    node_info = HDATA.NodeInfo()
    result = HAPI_LIB.HAPI_GetNodeInfo(
        byref(session), node_id, byref(node_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetNodeInfo Failed with {0}".format(HDATA.Result(result).name)
    return node_info


def get_node_path(session: HDATA.Session,
                  node_id: int,
                  relative_to_node_id: int = -1) \
        -> str:
    """Wrapper for HAPI_GetNodePath

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        relative_to_node_id (int): Set this to -1 to get the absolute path \
            of the node_id. Otherwise, the path will be relative to this node id.

    Returns:
        str: The returned path string, valid until the next call to this function.
    """
    pathsh = c_int32()

    result = HAPI_LIB.HAPI_GetNodePath(
        byref(session), node_id, relative_to_node_id, byref(pathsh))
    assert result == HDATA.Result.SUCCESS, \
        "GetNodePath Failed with {0}".format(HDATA.Result(result).name)
    return get_string(session, pathsh.value)


def get_asset_info(session: HDATA.Session, node_id):
    """Wrapper for HAPI_GetAssetInfo
    Fill an AssetInfo struct from a node.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        AssetInfo: AssetInfo of querying node
    """
    asset_info = HDATA.AssetInfo()
    result = HAPI_LIB.HAPI_GetAssetInfo(
        byref(session), c_int(node_id), byref(asset_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetAssetInfo Failed with {0}".format(HDATA.Result(result).name)
    return asset_info


def get_node_input_name(session: HDATA.Session,
                        node_id: int,
                        input_idx: int) \
        -> str:
    """Wrapper for HAPI_GetNodeInputName

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        input_idx (int): The input index.

    Returns:
        str: Input name string handle return value - valid until the next call to this function.
    """
    namesh = c_int32()
    result = HAPI_LIB.HAPI_GetNodeInputName(byref(session), c_int(node_id), c_int(input_idx), byref(namesh))
    assert result == HDATA.Result.SUCCESS, \
        "GetAssetInfo Failed with {0}".format(HDATA.Result(result).name)
    return get_string(session, namesh.value)


def get_parameters(session: HDATA.Session, 
                   node_id: int,
                   node_info: HDATA.NodeInfo) \
        -> List[HDATA.ParmInfo]:
    """Wrapper for HAPI_GetParameters
    Fill an array of HAPI_ParmInfo structs with parameter information from the asset instance node.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        node_info (NodeInfo): Nodeinfo of querying node

    Returns:
        Array of ParmInfo: Array of parminfo of querying node
    """
    if node_info.parmCount == 0:
        return []
    params = (HDATA.ParmInfo * node_info.parmCount)()
    result = HAPI_LIB.HAPI_GetParameters(
        byref(session), c_int(node_id), byref(params), c_int32(0), c_int32(node_info.parmCount))
    assert result == HDATA.Result.SUCCESS, \
        "GetParameters Failed with {0}".format(HDATA.Result(result).name)
    return params[:]


def get_parm_choice_lists(session: HDATA.Session,
                          node_id: int) \
        -> Array:
    """Wrapper for HAPI_GetParmChoiceLists
    Fill an array of HAPI_ParmChoiceInfo structs with parameter choice list information from the asset instance node.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.

    Returns:
        Array of HAPI_ParmChoiceInfo: Array of parmchoiceinfo of querying node
    """
    node_info: HDATA.NodeInfo = get_node_info(session, node_id)

    parm_choices_array = (HDATA.ParmChoiceInfo * node_info.parmChoiceCount)()
    if node_info.parmChoiceCount != 0:
        result = HAPI_LIB.HAPI_GetParmChoiceLists(byref(session), c_int(node_id), byref(parm_choices_array), 0,
                                                  node_info.parmChoiceCount)
        assert result == HDATA.Result.SUCCESS, \
            "GetParmChoiceLists Failed with {0}".format(HDATA.Result(result).name)
    return parm_choices_array


def get_parm_int_value(session: HDATA.Session, node_id, parmname, tupleid=0):
    """Wrapper for HAPI_GetParmIntValue
    Get single parm int value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.

    Returns:
        int: value of querying param
    """
    val = c_int32()
    result = HAPI_LIB.HAPI_GetParmIntValue(
        byref(session), node_id, c_char_p(parmname.encode('utf-8')), tupleid, byref(val))
    assert result == HDATA.Result.SUCCESS, \
        "GetParmIntValue Failed with {0}".format(
            HDATA.Result(result).name)
    return val.value


def get_parm_int_values(session: HDATA.Session, node_id, start, length = 1) -> List[int]:
    """Wrapper for HAPI_GetParmFloatValues
    
    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        start (int): First Index of range.
        length (int, optional): length of values to get
    
    Returns:
        Array
    """
    int_array = (c_int32 * length)()
    result = HAPI_LIB.HAPI_GetParmIntValues(
        byref(session), c_int32(node_id), byref(int_array), c_int32(start), c_int32(length))
    assert result == HDATA.Result.SUCCESS, \
        "GetParmIntValues Failed with {0}".format(
            HDATA.Result(result).name)
    
    return int_array[:]


def get_parm_float_value(session: HDATA.Session, node_id, parmname, tupleid=0):
    """Wrapper for HAPI_GetParmFloatValue
    Get single parm float value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.

    Returns:
        float: value of querying param
    """
    val = c_float()
    result = HAPI_LIB.HAPI_GetParmFloatValue(
        byref(session), node_id, c_char_p(parmname.encode('utf-8')), tupleid, byref(val))
    assert result == HDATA.Result.SUCCESS, \
        "GetParmFloatValue Failed with {0}".format(
            HDATA.Result(result).name)
    return val.value

def get_parm_float_values(session: HDATA.Session, node_id, start, length = 1) -> List[float]:
    """Wrapper for HAPI_GetParmFloatValues
    
    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        start (int): First Index of range.
        length (int, optional): length of values to get
    
    Returns:
        Array
    """
    float_array = (c_float * length)()
    result = HAPI_LIB.HAPI_GetParmFloatValues(
        byref(session), c_int(node_id), byref(float_array), c_int32(start), c_int32(length))
    assert result == HDATA.Result.SUCCESS, \
        "GetParmFloatValues Failed with {0}".format(
            HDATA.Result(result).name)
    
    return float_array[:]



def get_parm_string_value(session: HDATA.Session,
                          node_id: int,
                          parmname: str,
                          tupleid: int = 0) \
        -> str:
    """Wrapper for HAPI_GetParmStringValue
    Get single parm string value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.

    Returns:
        str: value of querying param
    """
    stringsh = c_int32()
    result = HAPI_LIB.HAPI_GetParmStringValue(
        byref(session), node_id, c_char_p(parmname.encode('utf-8')), c_int(tupleid), c_bool(True), byref(stringsh))
    assert result == HDATA.Result.SUCCESS, \
        "GetParamStringValue Failed with {0}".format(
            HDATA.Result(result).name)
    return get_string(session, stringsh.value)


def set_parm_int_value(session: HDATA.Session, node_id, parmname, value, tupleid=0):
    """Wrapper for HAPI_SetParmIntValue
    Set single parm int value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        value (int): Value to set to parm
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.
    """
    result = HAPI_LIB.HAPI_SetParmIntValue(
        byref(session), node_id, c_char_p(parmname.encode('utf-8')), tupleid, c_int(value))
    assert result == HDATA.Result.SUCCESS, \
        "SetParmIntValue Failed with {0}".format(
            HDATA.Result(result).name)


def set_parm_int_values(session: HDATA.Session, node_id, value, start):
    """Wrapper for HAPI_SetParmIntValues
    Set single parm int value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        value (npt.array): Value to set to parm
        start: First index of range.
    """
    intP = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetParmIntValues(
        byref(session), node_id, value.flatten().ctypes.data_as(intP), c_int32(start), c_int32(value.shape[0]))
    assert result == HDATA.Result.SUCCESS, \
        "SetParmIntValue Failed with {0}".format(
            HDATA.Result(result).name)


def set_parm_float_value(session: HDATA.Session, node_id, parmname, value, tupleid=0):
    """Wrapper for HAPI_SetParmFloatValue
    Set single parm float value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        value (float): Value to set to parm
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.
    """
    result = HAPI_LIB.HAPI_SetParmFloatValue(
        byref(session), c_int(node_id), c_char_p(parmname.encode('utf-8')), c_int(tupleid), c_float(value))
    assert result == HDATA.Result.SUCCESS, \
        "SetParmFloatValue Failed with {0}".format(
            HDATA.Result(result).name)


def set_parm_float_values(session: HDATA.Session, node_id, value: npt.NDArray, start):
    """Wrapper for HAPI_SetParmFloatValues
    Set single parm int value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        value (npt.array): Value to set to parm
        start: First index of range.
    """
    floatP = POINTER(c_float)
    value = value.astype(np.float32)
    result = HAPI_LIB.HAPI_SetParmFloatValues(
        byref(session), c_int(node_id), value.flatten().ctypes.data_as(floatP), c_int32(start), c_int32(value.shape[0]))
    assert result == HDATA.Result.SUCCESS, \
        "SetParmIntValue Failed with {0}".format(
            HDATA.Result(result).name)
    

def set_parm_string_value(session: HDATA.Session,
                          node_id: int,
                          parmid: int,
                          value: str,
                          tupleid: int = 0):
    """Wrapper for HAPI_SetParmStringValue
    Set single parm str value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmid (int): The parm name.
        value (str): Value to set to parm
        tupleid (int, optional): Index within the parameter's values tuple.. Defaults to 0.
    """
    result = HAPI_LIB.HAPI_SetParmStringValue(
        byref(session), c_int(node_id), c_char_p(value.encode('utf-8')), c_int(parmid), c_int(tupleid))
    assert result == HDATA.Result.SUCCESS, \
        "SetParamStringValue Failed with {0}".format(
            HDATA.Result(result).name)


def set_parm_node_value(session: HDATA.Session,
                        node_id: int,
                        parmname: str,
                        value: int):
    """Wrapper for HAPI_SetParmIntValue
    Set single parm int value by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        parmname (str): The parm name.
        value (int): The node id to set to parm
    """
    result = HAPI_LIB.HAPI_SetParmNodeValue(
        byref(session), c_int(node_id), c_char_p(parmname.encode('utf-8')), c_int(value))
    assert result == HDATA.Result.SUCCESS, \
        "SetParamNodeValue Failed with {0}".format(
            HDATA.Result(result).name)


def insert_multiparm_instance(session: HDATA.Session,
                              node_id: int,
                              parm_id: int,
                              instance_position: int):
    """Wrapper for HAPI_SetPartInfo
    Insert an instance of a multiparm before instance_position.
    Args:
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      node_id
                     The node id.
        @param[in]      parm_id
                     A parm id given by a ::HAPI_ParmInfo struct that
                     has type ::HAPI_PARMTYPE_MULTIPARMLIST.
        @param[in]      instance_position
                     The new instance will be inserted at this position
                     index. Do note the multiparms can start at position
                     1 or 0. Use ::HAPI_ParmInfo::instanceStartOffset to
                     distinguish.
    """
    result = HAPI_LIB.HAPI_InsertMultiparmInstance(
        byref(session), c_int(node_id), c_int(parm_id), c_int(instance_position))
    assert result == HDATA.Result.SUCCESS, \
        "SetPartInfo Failed with {0}".format(HDATA.Result(result).name)


def remove_multiparm_instance(session: HDATA.Session,
                              node_id: int,
                              parm_id: int,
                              instance_position: int):
    '''
    @brief Remove the instance of a multiparm given by instance_position.
    @ingroup Parms
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      node_id
                     The node id.
        @param[in]      parm_id
                     A parm id given by a ::HAPI_ParmInfo struct that
                     has type ::HAPI_PARMTYPE_MULTIPARMLIST.
        @param[in]      instance_position
                     The instance at instance_position will removed.
    '''
    result = HAPI_LIB.HAPI_RemoveMultiparmInstance(
        byref(session), c_int(node_id), c_int(parm_id), c_int(instance_position))
    assert result == HDATA.Result.SUCCESS, \
        "SetPartInfo Failed with {0}".format(HDATA.Result(result).name)


def get_instance_object_ids(session: HDATA.Session,
                            node_id: int, instanced_node_id_array, start: int, length: int):
    '''
    @brief  Get the node ids for the objects being instanced by an
         Instance OBJ node.
    @ingroup Objects
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      object_node_id
                     The object node id.
        @param[out]     instanced_node_id_array
                     Array of ::HAPI_NodeId at least the size of length.
        @param[in]      start
                     At least @c 0 and at most @c object_count returned by
                     ::HAPI_ComposeObjectList().
                     <!-- default 0 -->
        @param[in]      length
                     Given @c object_count returned by
                     ::HAPI_ComposeObjectList(), @c length should be at least
                     @c 0 and at most <tt>object_count - start</tt>.
                     <!-- source ::HAPI_ComposeObjectList - start -->
    '''
    result = HAPI_LIB.HAPI_GetInstancedObjectIds(
        byref(session), node_id, byref(instanced_node_id_array), start, length)
    assert result == HDATA.Result.SUCCESS, \
        "SetPartInfo Failed with {0}".format(HDATA.Result(result).name)


def get_instance_transforms(session: HDATA.Session,
                            node_id: int,
                            rst_order: HDATA.RSTOrder,
                            start: int,
                            length: int) \
        -> Array:
    '''wrapper for HAPI_GetInstanceTransforms()

    @deprecated Use HAPI_GetInstanceTransformsOnPart() instead (using Part 0 for
             previous behaviour).
        Fill an array of ::HAPI_Transform structs with the transforms
         of each instance of this instancer object.

    Args:
        session:      The session of Houdini you are interacting with.

        node_id:      The object node id.
        rst_order:    The order of application of translation, rotation and\
                     scale.

        start:        First index of range. Must be at least 0 and at
                     most ::HAPI_PartInfo::pointCount - 1. This is the 0th
                     part of the display geo of the instancer object node.

        length:       Must be at least 0 and at most
                     ::HAPI_PartInfo::pointCount - @p start. This is the 0th
                     part of the display geo of the instance object node.

    Returns:
        (Array) of ::HAPI_Transform at least the size of length.
    '''
    # @modify:highyang use supported function to replace the deprecated one
    transforms_array = (HDATA.Transform * length)()
    result = HAPI_LIB.HAPI_GetInstanceTransformsOnPart(
        byref(session), c_int(node_id), c_int(0), rst_order, transforms_array, c_int(start), c_int(length))
    assert result == HDATA.Result.SUCCESS, \
        "GetInstanceTransformsOnPart with {0}".format(HDATA.Result(result).name)
    return transforms_array


def get_instance_transforms_on_part(session: HDATA.Session,
                                    node_id: int,
                                    part_id: int,
                                    rst_order: HDATA.RSTOrder,
                                    start: int,
                                    length: int) \
        -> Array:
    '''wrapper for HAPI_GetInstanceTransformsOnPart()

        Fill an array of ::HAPI_Transform structs with the transforms
         of each instance of this instancer object on given part.

    Args:
        session:      The session of Houdini you are interacting with.

        node_id:      The object node id.
        part_id:      The part id
        rst_order:    The order of application of translation, rotation and\
                     scale.

        start:        First index of range. Must be at least 0 and at
                     most ::HAPI_PartInfo::pointCount - 1. This is the 0th
                     part of the display geo of the instancer object node.

        length:       Must be at least 0 and at most
                     ::HAPI_PartInfo::pointCount - @p start. This is the 0th
                     part of the display geo of the instance object node.

    Returns:
        (Array) of ::HAPI_Transform at least the size of length.
    '''
    transforms_array = (HDATA.Transform * length)()
    result = HAPI_LIB.HAPI_GetInstanceTransformsOnPart(
        byref(session), c_int(node_id), c_int(part_id), rst_order, transforms_array, c_int(start), c_int(length))
    assert result == HDATA.Result.SUCCESS, \
        "SetPartInfo Failed with {0}".format(HDATA.Result(result).name)
    return transforms_array


def set_part_info(session: HDATA.Session, node_id: int, part_info: HDATA.PartInfo, part_id: int = 0):
    """Wrapper for HAPI_SetPartInfo
    Set the main part info struct

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_info ([type]): [description]
        part_id : currently not used, just pass 0
    """
    result = HAPI_LIB.HAPI_SetPartInfo(
        byref(session), c_int(node_id), c_int(part_id), byref(part_info))
    assert result == HDATA.Result.SUCCESS, \
        "SetPartInfo Failed with {0}".format(HDATA.Result(result).name)


def set_curve_info(session: HDATA.Session, node_id, curve_info):
    """Wrapper for HAPI_SetCurveInfo
    Set meta-data for the curve mesh, including the curve type, order, and periodicity.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        curve_info (CurveInfo): CurveInfo to set to node
    """
    result = HAPI_LIB.HAPI_SetCurveInfo(
        byref(session), node_id, 0, byref(curve_info))
    assert result == HDATA.Result.SUCCESS, \
        "SetCurveInfo Failed with {0}".format(HDATA.Result(result).name)


def set_curve_counts(session: HDATA.Session, node_id, part_id, curve_count):
    """Wrapper for HAPI_SetCurveCounts
    Set the number of vertices for each curve in the part.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Currently unused. Input asset geos are assumed to have only one part.
        curve_count (ndarray): The number of cvs each curve contains.
    """
    intp = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetCurveCounts(
        byref(session), c_int(node_id), c_int(part_id),
        curve_count.flatten().ctypes.data_as(intp), c_int(0), curve_count.shape[0])
    assert result == HDATA.Result.SUCCESS, \
        "SetCurveCounts Failed with {0}".format(HDATA.Result(result).name)


def set_curve_knots(session: HDATA.Session, node_id, part_id, curve_knots):
    """Wrapper for HAPI_SetCurveKnots
    Set the knots of the curves in this part.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Currently unused. Input asset geos are assumed to have only one part.
        curve_knots (np.ndarray(int)): The knots of each curve.
    """
    if not isinstance(curve_knots, np.ndarray):
        return
    intp = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetCurveKnots(
        byref(session), node_id, part_id,
        curve_knots.flatten().ctypes.data_as(intp), 0, curve_knots.shape[0])
    assert result == HDATA.Result.SUCCESS, \
        "SetCurveKnots Failed with {0}".format(HDATA.Result(result).name)
    return


def get_curve_info(session: HDATA.Session, node_id, part_id):
    """Wrapper for HAPI_SetCurveInfo
    Retrieve any meta-data about the curves, including the curve's type, order, and periodicity.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part id of geo

    Returns:
        CurveInfo: CurveInfo of this node
    """
    curve_info = HDATA.CurveInfo()
    result = HAPI_LIB.HAPI_GetCurveInfo(
        byref(session), node_id, part_id, byref(curve_info))
    assert result == HDATA.Result.SUCCESS, \
        "GetCurveInfo Failed with {0}".format(HDATA.Result(result).name)
    return curve_info


def get_curve_counts(session: HDATA.Session, node_id, part_id, curve_count):
    """Wrapper for HAPI_GetCurveCounts
    Retrieve the number of vertices for each curve in the part.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Currently unused. Input asset geos are assumed to have only one part.
        curve_count (int): The number of cvs each curve contains.

    Returns:
        ndarray: number of vertices for each curve
    """
    data_buffer = (c_int32 * curve_count)()
    result = HAPI_LIB.HAPI_GetCurveCounts(
        byref(session), node_id, part_id,
        byref(data_buffer), 0, curve_count)
    assert result == HDATA.Result.SUCCESS, \
        "GetCurveCounts Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int32)
    return data_np


def get_curve_knots(session: HDATA.Session, node_id, part_id, knot_length):
    """Wrapper for HAPI_GetCurveKnots
    Retrieve the knots of the curves in this part.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Currently unused. Input asset geos are assumed to have only one part.
        knot_length (int): The number of knots in curve

    Returns:
        ndarray: knots for each curve
    """
    data_buffer = (c_float * knot_length)()
    result = HAPI_LIB.HAPI_GetCurveKnots(
        byref(session), node_id, part_id,
        byref(data_buffer), 0, knot_length)
    assert result == HDATA.Result.SUCCESS, \
        "GetCurveKnots Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.float32)
    return data_np


def get_input_curve_info(session: HDATA.Session,
                         node_id: int,
                         part_id: int) \
        -> HDATA.InputCurveInfo:
    """Wrapper of HAPI_GetInputCurveInfo @highyang

        Retrieve meta-data about the input curves, including the\
        curve's type, order, and whether the curve is closed and reversed.

    Args:
        session (Session) : the session of Houdini you are interacting with
        node_id (int) : the node id
        part_id (int) : the part id


    Returns:
        info (InputCurveInfo) : the curve info represents the meta-data about\
                the curves, including the type, order, and closed and reversed values
    """
    info = HDATA.InputCurveInfo()
    result = HAPI_LIB.HAPI_GetInputCurveInfo(byref(session),
                                             c_int(node_id),
                                             c_int(part_id),
                                             byref(info))
    assert HDATA.Result.SUCCESS == result, \
        "GetInputCurveInfo Failed with {0}".format(HDATA.Result(result).name)
    return info


def set_input_curve_info(session: HDATA.Session,
                         node_id: int,
                         part_id: int,
                         info: HDATA.InputCurveInfo):
    """Wrapper of HAPI_SetInputCurveInfo @highyang

            Sets the positions for input curves, doing checks for
        curve validity, and adjusting the curve settings accordingly.
        Will also cook the node.

    Args:
        session (Session) : the session of Houdini you are interacting with
        node_id (int) : the node id
        part_id (int) : the part id
        info (InputCurveInfo) : the curve info represents the meta-data about\
                the curves, including the type, order, and closed and reversed values

    Returns:
        None
    """
    result = HAPI_LIB.HAPI_SetInputCurveInfo(byref(session),
                                             c_int(node_id),
                                             c_int(part_id),
                                             byref(info))
    assert HDATA.Result.SUCCESS == result, \
        "SetInputCurveInfo Failed with {0}".format(HDATA.Result(result).name)


def set_input_curve_positions(session: HDATA.Session,
                              node_id: int,
                              part_id: int,
                              positions_array: npt.NDArray[float],
                              start: int = 0,
                              length: int = None):
    """Wrapper for HAPI_SetInputCurvePositions @highyang
    Sets the positions for input curves, doing checks for curve validity,
        and adjusting the curve settings accordingly. Will also cook the node

    Args:
        session : the session of Houdini you are interacting with.
        node_id (int): The node id.
        part_id (int): Currently unused. Input asset geos are assumed to
            have only one part.
        positions_array : A float array representing the positions attribute.
            It will read the array assuming a tuple size of 3. Note that this
            function does not do any coordinate axes conversion.
        start : The index of the first position in positions_array.
        length : The size of the positions array.

    Returns:
         None

    """
    floatP = POINTER(c_float)
    if length is None:
        length = positions_array.shape[0] * 3

    result = HAPI_LIB.HAPI_SetInputCurvePositions(byref(session),
                                                  c_int(node_id),
                                                  c_int(part_id),
                                                  positions_array.flatten().ctypes.data_as(floatP),
                                                  c_int(start),
                                                  c_int(length))
    assert HDATA.Result.SUCCESS == result, \
        "SetInputCurvePositions Failed with {0}".format(HDATA.Result(result).name)


def set_input_curve_positions_rotations_scales(session: HDATA.Session,
                                               node_id: int,
                                               part_id: int,
                                               positions_array: npt.NDArray[float],
                                               rotations_array: npt.NDArray[float],
                                               scales_array: npt.NDArray[float],
                                               positions_start: int = 0,
                                               positions_length: int = None,
                                               rotations_start: int = 0,
                                               rotations_length: int = None,
                                               scales_start: int = 0,
                                               scales_length: int = None):
    """Wrapper for HAPI_SetInputCurvePositions @highyang
    Sets the positions for input curves, doing checks for curve validity,
        and adjusting the curve settings accordingly. Will also cook the node

    Args:
        session : the session of Houdini you are interacting with.
        node_id (int): The node id.
        part_id (int): Currently unused. Input asset geos are assumed to
            have only one part.
        positions_array : A float array representing the positions attribute.
            It will read the array assuming a tuple size of 3. Note that this
            function does not do any coordinate axes conversion.
        start : The index of the first position in positions_array.
        length : The size of the positions array.

    Returns:
         None

    """
    floatP = POINTER(c_float)
    if positions_length is None:
        positions_length = positions_array.shape[0] * 3
    if rotations_length is None:
        rotations_length = rotations_array.shape[0] * 4
    if scales_length is None:
        scales_length = scales_array.shape[0] * 3

    result = HAPI_LIB.HAPI_SetInputCurvePositions(byref(session),
                                                  c_int(node_id),
                                                  c_int(part_id),
                                                  positions_array.flatten().ctypes.data_as(floatP),
                                                  c_int(positions_start),
                                                  c_int(positions_length),
                                                  rotations_array.flatten().ctypes.data_as(floatP),
                                                  c_int(rotations_start),
                                                  c_int(rotations_length),
                                                  scales_array.flatten().ctypes.data_as(floatP),
                                                  c_int(scales_start),
                                                  c_int(scales_length),
                                                  )
    assert HDATA.Result.SUCCESS == result, \
        "SetInputCurvePositionsRotationsScales Failed with {0}".format(HDATA.Result(result).name)


def add_attribute(session: HDATA.Session,
                  node_id: int,
                  name: str,
                  attrib_info: HDATA.AttributeInfo):
    """Wrapper for HAPI_AddAttribute
    Add an attribute to node

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to add
    """
    result = HAPI_LIB.HAPI_AddAttribute(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')), byref(attrib_info))
    assert result == HDATA.Result.SUCCESS, \
        "AddAttribute Failed with {0}".format(HDATA.Result(result).name)


def set_attribute_float_data(session: HDATA.Session, node_id, name, attrib_info, data):
    """Wrapper for HAPI_SetAttributeFloatData
    Set attribute float data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to set
        data (np.ndarry(float)): Data to set in as attribute
    """
    floatp = POINTER(c_float)
    result = HAPI_LIB.HAPI_SetAttributeFloatData(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
        byref(attrib_info), data.flatten().ctypes.data_as(floatp), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "SetAttributeFloatData Failed with {0}".format(
            HDATA.Result(result).name)


def set_attribute_float64_data(session: HDATA.Session, node_id, name, attrib_info, data):
    """Wrapper for HAPI_SetAttributeFloat64Data
    Set attribute float64 data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to set
        data (np.ndarry(float64)): Data to set in as attribute
    """
    floatp = POINTER(c_double)
    result = HAPI_LIB.HAPI_SetAttributeFloat64Data(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
        byref(attrib_info), data.flatten().ctypes.data_as(floatp), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "SetAttributeFloat64Data Failed with {0}".format(
            HDATA.Result(result).name)


def set_attribute_int64_data(session: HDATA.Session, node_id, name, attrib_info, data):
    """Wrapper for HAPI_SetAttributeInt64Data
    Set attribute int64 data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to set
        data (np.ndarry(int64)): Data to set in as attribute
    """
    intp = POINTER(c_int64)
    result = HAPI_LIB.HAPI_SetAttributeInt64Data(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
        byref(attrib_info), data.flatten().ctypes.data_as(intp), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "SetAttributeInt64Data Failed with {0}".format(
            HDATA.Result(result).name)


def set_attribute_int_data(session: HDATA.Session, node_id, name, attrib_info, data):
    """Wrapper for HAPI_SetAttributeIntData
    Set attribute int data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to set
        data (np.ndarry(int)): Data to set in as attribute
    """
    intp = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetAttributeIntData(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
        byref(attrib_info), data.flatten().ctypes.data_as(intp), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "SetAttributeIntData Failed with {0}".format(
            HDATA.Result(result).name)


def set_attribute_string_data(session: HDATA.Session, node_id, name, attrib_info, data):
    """Wrapper for HAPI_SetAttributeStringData
    Set attribute str data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        name (str): Attribute name
        attrib_info (AttributeInfo): Attribute property to set
        data (str): Data to set in as attribute
    """
    # charp = POINTER(c_char_p)
    temp = data.flatten()

    # result = HAPI_LIB.HAPI_SetAttributeStringData(
    #    byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
    #    byref(attrib_info), temp.ctypes.data_as(charp), 0, attrib_info.count)

    # kivlin, fix string, we need to use create_string_buffer. see https://stackoverflow.com/questions/16699800/python-using-ctypes-to-pass-a-char-array-and-populate-results
    c_data = [create_string_buffer(value) for value in temp]
    c_data_pointer = (ctypes.c_char_p * len(c_data))(*map(ctypes.addressof, c_data))
    result = HAPI_LIB.HAPI_SetAttributeStringData(
        byref(session), node_id, 0, c_char_p(name.encode('utf-8')),
        byref(attrib_info), c_data_pointer, 0, attrib_info.count)

    assert result == HDATA.Result.SUCCESS, \
        "SetAttributeStringData Failed with {0}".format(
            HDATA.Result(result).name)


STORAGE_TYPE_TO_SET_ATTRIB = {
    HDATA.StorageType.INT: set_attribute_int_data,
    HDATA.StorageType.INT64: set_attribute_int64_data,
    HDATA.StorageType.FLOAT: set_attribute_float_data,
    HDATA.StorageType.FLOAT64: set_attribute_float64_data,
    HDATA.StorageType.STRING: set_attribute_string_data
}


def get_attribute_names(session: HDATA.Session, node_id, part_info,
                        attrib_type=HDATA.AttributeOwner.POINT):
    """Wrapper for HAPI_GetAttributeNames
    Get list of attribute names by attribute owner. \
        Note that the name string handles are only valid \
            until the next time this function is called.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_info (PartInfo): The part info
        attrib_type (AttributeOwner, optional): Type of attribute. \
            Defaults to HDATA.AttributeOwner.POINT.
    """
    attrib_names = []
    attrib_count = part_info.attributeCounts[attrib_type]
    if attrib_count == 0:
        return attrib_names
    string_handle_buffer = (c_int32 * attrib_count)()
    result = HAPI_LIB.HAPI_GetAttributeNames(
        byref(session), node_id, part_info.id, attrib_type,
        byref(string_handle_buffer), attrib_count)
    assert result == HDATA.Result.SUCCESS, \
        "GetAttributeNames Failed with {0}".format(
            HDATA.Result(result).name)
    attrib_names = []
    for string_handle in string_handle_buffer:
        attrib_names.append(get_string(session, string_handle))
    return attrib_names


def get_attribute_info(session: HDATA.Session, node_id, part_id, name, attrib_type):
    """Wrapper for HAPI_GetAttributeInfo
    Get the attribute info struct for the attribute specified by name.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part info
        name (str): Name of attribute querying
        attrib_type (AttributeOwner, optional): Type of attribute.

    Returns:
        AttributeInfo: AttributeInfo of querying named attribute in node
    """
    attrib_info = HDATA.AttributeInfo()
    result = HAPI_LIB.HAPI_GetAttributeInfo(byref(session), c_int(node_id), c_int(part_id),
                                            c_char_p(name.encode('utf-8')), attrib_type, byref(attrib_info))
    assert result == HDATA.Result.SUCCESS, "GetAttributeInfo Failed with {0}".format(HDATA.Result(result).name)
    return attrib_info


def get_attribute_int_data(session: HDATA.Session, node_id, part_id, name, attrib_info):
    """Wrapper for HAPI_GetAttributeIntData
    Get attribute integer data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part Id
        name (str): Name of attribute querying
        attrib_info (AttributeInfo): AttributeInfo of querying named attribute in node

    Returns:
        np.ndarray(int): data of querying named attribute
    """
    data_buffer = (c_int32 * (attrib_info.count * attrib_info.tupleSize))()
    result = HAPI_LIB.HAPI_GetAttributeIntData(
        byref(session), node_id, part_id, c_char_p(name.encode('utf-8')),
        byref(attrib_info), -1, byref(data_buffer), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "GetAttributeIntData Failed with {0}".format(
            HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int32)
    return np.reshape(data_np, (attrib_info.count, attrib_info.tupleSize))


def get_attribute_int64_data(session: HDATA.Session, node_id, part_id, name, attrib_info):
    """Wrapper for HAPI_GetAttributeInt64Data
    Get attribute int64 data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part Id
        name (str): Name of attribute querying
        attrib_info (AttributeInfo): AttributeInfo of querying named attribute in node

    Returns:
        np.ndarray(int64): data of querying named attribute
    """
    data_buffer = (c_int64 * (attrib_info.count * attrib_info.tupleSize))()
    result = HAPI_LIB.HAPI_GetAttributeInt64Data(
        byref(session), node_id, part_id, c_char_p(name.encode('utf-8')),
        byref(attrib_info), -1, byref(data_buffer), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "GetAttributeInt64Data Failed with {0}".format(
            HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int64)
    return np.reshape(data_np, (attrib_info.count, attrib_info.tupleSize))


def get_attribute_float_data(session: HDATA.Session, node_id, part_id, name, attrib_info):
    """Wrapper for HAPI_GetAttributeFloatData
    Get attribute float data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part Id
        name (str): Name of attribute querying
        attrib_info (AttributeInfo): AttributeInfo of querying named attribute in node

    Returns:
        np.ndarray(float): data of querying named attribute
    """
    data_buffer = (c_float * (attrib_info.count * attrib_info.tupleSize))()
    result = HAPI_LIB.HAPI_GetAttributeFloatData(byref(session), c_int(node_id), c_int(part_id),
                                                 c_char_p(name.encode('utf-8')), byref(attrib_info),
                                                 -1, byref(data_buffer),
                                                 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, "GetAttributeFloatData Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.float32)
    return np.reshape(data_np, (attrib_info.count, attrib_info.tupleSize))


def get_attribute_float64_data(session: HDATA.Session, node_id, part_id, name, attrib_info):
    """Wrapper for HAPI_GetAttributeFloat64Data
    Get attribute float64 data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part Id
        name (str): Name of attribute querying
        attrib_info (AttributeInfo): AttributeInfo of querying named attribute in node

    Returns:
        np.ndarray(float64): data of querying named attribute
    """
    data_buffer = (c_double * (attrib_info.count * attrib_info.tupleSize))()
    result = HAPI_LIB.HAPI_GetAttributeFloat64Data(
        byref(session), node_id, part_id, c_char_p(name.encode('utf-8')),
        byref(attrib_info), -1, byref(data_buffer), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, \
        "GetAttributeFloat64Data Failed with {0}".format(
            HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.float64)
    return np.reshape(data_np, (attrib_info.count, attrib_info.tupleSize))


def get_attribute_string_data(session: HDATA.Session, node_id, part_id, name, attrib_info):
    """Wrapper for HAPI_GetAttributeStringData
    Get attribute string data.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): Part Id
        name (str): Name of attribute querying
        attrib_info (AttributeInfo): AttributeInfo of querying named attribute in node

    Returns:
        np.ndarray(str): data of querying named attribute
    """
    data_buffer = (c_int32 * (attrib_info.count * attrib_info.tupleSize))()
    result = HAPI_LIB.HAPI_GetAttributeStringData(
        byref(session), c_int(node_id), c_int(part_id), c_char_p(name.encode('utf-8')),
        byref(attrib_info), byref(data_buffer), 0, attrib_info.count)
    assert result == HDATA.Result.SUCCESS, "GetAttributeStringData Failed with {0}".format(HDATA.Result(result).name)
    data_str = np.ndarray((attrib_info.count * attrib_info.tupleSize), dtype=np.dtype('O'))
    for i in range(0, attrib_info.count * attrib_info.tupleSize):
        data_str[i] = get_string(session, data_buffer[i])
    return np.reshape(data_str, (attrib_info.count, attrib_info.tupleSize))


STORAGE_TYPE_TO_GET_ATTRIB = {
    HDATA.StorageType.INT: get_attribute_int_data,
    HDATA.StorageType.INT64: get_attribute_int64_data,
    HDATA.StorageType.FLOAT: get_attribute_float_data,
    HDATA.StorageType.FLOAT64: get_attribute_float64_data,
    HDATA.StorageType.STRING: get_attribute_string_data
}


def set_vertex_list(session: HDATA.Session, node_id, vertex_list_array):
    """Wrapper for HAPI_SetVertexList
    Set array containing the vertex-point associations where \
        the ith element in the array is the point index the \
            ith vertex associates with.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        vertex_list_array (ndarray(int)): Vertex list to set
    """
    intp = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetVertexList(
        byref(session), node_id, 0, vertex_list_array.flatten().ctypes.data_as(intp),
        0, np.size(vertex_list_array))
    assert result == HDATA.Result.SUCCESS, \
        "SetVertexList Failed with {0}".format(HDATA.Result(result).name)


def set_face_counts(session: HDATA.Session, node_id, face_counts_array):
    """Wrapper for HAPI_SetFaceCounts
    Set the array of faces where the nth integer in the array \
        is the number of vertices the nth face has.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        face_counts_array (ndarray(int)): face count to set
    """
    intp = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetFaceCounts(
        byref(session), node_id, 0, face_counts_array.ctypes.data_as(intp),
        0, face_counts_array.shape[0])
    assert result == HDATA.Result.SUCCESS, \
        "SetFaceCounts Failed with {0}".format(HDATA.Result(result).name)


def get_vertex_list(session: HDATA.Session, node_id, part_info):
    """Wrapper for HAPI_GetVertexList

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_info (PartInfo): Part info of querying

    Returns:
        np.ndarray: Array of vertices
    """
    data_buffer = (c_int * part_info.vertexCount)()
    result = HAPI_LIB.HAPI_GetVertexList(
        byref(session), node_id, part_info.id, byref(data_buffer),
        0, part_info.vertexCount)
    assert result == HDATA.Result.SUCCESS, \
        "GetVertexList Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int32)
    return data_np


def get_face_counts(session: HDATA.Session, node_id, part_info):
    """Wrapper for HAPI_GetFaceCounts
    Get the array of faces where the nth integer in the array \
        is the number of vertices the nth face has.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_info (PartInfo): Part info of querying

    Returns:
        np.ndarray: Array of face count
    """
    data_buffer = (c_int * part_info.faceCount)()
    result = HAPI_LIB.HAPI_GetFaceCounts(
        byref(session), node_id, part_info.id, byref(data_buffer),
        0, part_info.faceCount)
    assert result == HDATA.Result.SUCCESS, \
        "GetFaceCounts Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int32)
    return data_np


def get_faces(session: HDATA.Session, node_id, part_info):
    """Get faces of a part

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_info (PartInfo): Part info of querying

    Returns:
        np.ndarray: 2D Array of vertex list
    """
    vertex_list = get_vertex_list(session, node_id, part_info)
    face_counts = get_face_counts(session, node_id, part_info)
    for i in range(1, len(face_counts)):
        face_counts[i] = face_counts[i - 1] + face_counts[i]
    faces = np.split(vertex_list, face_counts)
    return faces[:-1]  # remove last empty face


def set_heightfield_data(session: HDATA.Session, node_id, part_id, name, data_array):
    """Wrapper for HAPI_SetHeightFieldData
    Set the height field data for a terrain volume with the values from\
         a flattened 2D array of float.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part id
        name (str): The name of the volume used for the heightfield. \
            If set to "height" the values will be used for height information, \
                if not, the data will used as a mask.
        data_array (np.ndarray): volume info of this node

    """
    floatp = POINTER(c_float)
    result = HAPI_LIB.HAPI_SetHeightFieldData(
        byref(session), node_id, part_id, c_char_p(name.encode('utf-8')),
        data_array.flatten().ctypes.data_as(floatp), 0, np.size(data_array))
    assert result == HDATA.Result.SUCCESS, \
        "SetHeightfieldData Failed with {0}".format(HDATA.Result(result).name)


def get_heightfield_data(session: HDATA.Session, node_id, part_id, volume_info):
    """Wrapper for HAPI_GetHeightFieldData
    Get the height field data for a terrain volume as a flattened 2D array of float heights

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        volume_info (VolumeInfo): volume info of this node

    Returns:
        np.ndarray: data of this heightfield
    """
    data_size = volume_info.xLength * volume_info.yLength * volume_info.tupleSize
    data_buffer = (c_float * data_size)()
    result = HAPI_LIB.HAPI_GetHeightFieldData(
        byref(session), node_id, part_id,
        byref(data_buffer), 0, data_size)
    assert result == HDATA.Result.SUCCESS, \
        "GetHeightfieldData Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.float32)
    data_np = data_np.reshape(volume_info.xLength, volume_info.yLength, volume_info.tupleSize)
    return data_np


def get_volume_tile_float_data(session: HDATA.Session, node_id, part_id, volume_tile_info, tuple_size):
    """Wrapper for HAPI_GetVolumeTileFloatData
    Retrieve floating point values of the voxels pointed to by a tile.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        volume_tile_info (VolumeTileInfo): The tile to retrieve
        tuple_size (int): The tuple size of data

    Returns:
        np.ndarray: data of this volume tile
    """
    data_size = 8 * 8 * 8 * tuple_size
    data_buffer = (c_float * data_size)()
    result = HAPI_LIB.HAPI_GetVolumeTileFloatData(
        byref(session), node_id, part_id, c_float(-8.8), byref(volume_tile_info),
        byref(data_buffer), data_size)
    assert result == HDATA.Result.SUCCESS, \
        "GetVolumeTileFloatData Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.float32)
    return data_np


def get_volume_tile_int_data(session: HDATA.Session, node_id, part_id, volume_tile_info, tuple_size):
    """Wrapper for HAPI_GetVolumeTileIntData
    Retrieve floating point values of the voxels pointed to by a tile.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        volume_tile_info (VolumeTileInfo): The tile to retrieve
        tuple_size (int): The tuple size of data

    Returns:
        np.ndarray: data of this volume tile
    """
    data_size = 8 * 8 * 8 * tuple_size
    data_buffer = (c_int32 * data_size)()
    result = HAPI_LIB.HAPI_GetVolumeTileIntData(
        byref(session), node_id, part_id, c_int(-8), byref(volume_tile_info),
        byref(data_buffer), data_size)
    assert result == HDATA.Result.SUCCESS, \
        "GetVolumeTileIntData Failed with {0}".format(HDATA.Result(result).name)
    data_np = np.frombuffer(data_buffer, np.int32)
    return data_np


def set_volume_tile_float_data(session: HDATA.Session, node_id, part_id, volume_tile_info, data_array,
                               tuple_size):  # pylint: disable=too-many-arguments
    """Wrapper for HAPI_SetVolumeTileFloatData
    Retrieve floating point values of the voxels pointed to by a tile.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part to get.
        volume_tile_info (VolumeTileInfo): The tile to set
        volume_tile_info (VolumeTileInfo): The data to set
        tuple_size (int): The tuple size of data
    """
    floatp = POINTER(c_float)
    data_size = 8 * 8 * 8 * tuple_size
    result = HAPI_LIB.HAPI_SetVolumeTileFloatData(
        byref(session), node_id, part_id, byref(volume_tile_info),
        data_array.flatten().ctypes.data_as(floatp), data_size)
    assert result == HDATA.Result.SUCCESS, \
        "SetVolumeTileIntData Failed with {0}".format(HDATA.Result(result).name)


def set_volume_tile_int_data(session: HDATA.Session, node_id, part_id, volume_tile_info, data_array, tuple_size):
    """Wrapper for HAPI_SetVolumeTileIntData
    Retrieve floating point values of the voxels pointed to by a tile.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        part_id (int): The part to get.
        volume_tile_info (VolumeTileInfo): The tile to set
        volume_tile_info (VolumeTileInfo): The data to set
        tuple_size (int): The tuple size of data
    """
    intp = POINTER(c_int)
    data_size = 8 * 8 * 8 * tuple_size
    result = HAPI_LIB.HAPI_SetVolumeTileIntData(
        byref(session), node_id, part_id, byref(volume_tile_info),
        data_array.flatten().ctypes.data_as(intp), data_size)
    assert result == HDATA.Result.SUCCESS, \
        "SetVolumeTileIntData Failed with {0}".format(HDATA.Result(result).name)


def commit_geo(session: HDATA.Session, node_id):
    """Wrapper for HAPI_CommitGeo
    Commit the current input geometry to the cook engine. \
        Nodes that use this geometry node will re-cook using the input \
            geometry given through the geometry setter API calls.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
    """
    result = HAPI_LIB.HAPI_CommitGeo(byref(session), node_id)
    assert result == HDATA.Result.SUCCESS, \
        "CommitGeo Failed with {0}".format(HDATA.Result(result).name)


def rename_node(session: HDATA.Session, node_id, new_name):
    """Wrapper for HAPI_RenameNode
    Rename a node that you created. Only nodes with their \
        HAPI_NodeInfo::createdPostAssetLoad set to true can be renamed this way.

    Args:
        session (Session): The session of Houdini you are interacting with.
        node_id (int): The node to get.
        new_name (str): New name of node.
    """
    result = HAPI_LIB.HAPI_RenameNode(byref(session), node_id, c_char_p(new_name.encode('utf-8')))
    assert result == HDATA.Result.SUCCESS, \
        "Renamed Failed with {0}".format(HDATA.Result(result).name)


def save_hip_file(session: HDATA.Session, hipname: str, lock_nodes: bool=True):
    """Wrapper for HAPI_SaveHIPFile
    Saves a .hip file of the current Houdini scene.

    Args:
        session (Session): The session of Houdini you are interacting with.
        hipname (str): Name of saved hip file
        lock_nodes (bool, optional): Specify whether to lock all SOP nodes \
            before saving the scene file. This way, when you load the scene \
                file you can see exactly the state of each SOP at the time it \
                    was saved instead of relying on the re-cook to accurately \
                        reproduce the state. It does, however, take a lot more \
                            space and time locking all nodes like this.\
                                Defaults to False.
    """
    result = HAPI_LIB.HAPI_SaveHIPFile(
        byref(session), c_char_p(hipname.encode('utf-8')), c_bool(lock_nodes))
    assert result == HDATA.Result.SUCCESS, \
        "SaveHIPFile Failed with {0}".format(HDATA.Result(result).name)


def load_hip_file(session: HDATA.Session, hipname, cook_on_load):
    """
       @brief  Loads a .hip file into the main Houdini scene.

               @note In threaded mode, this is an _async call_!

               @note This method will load the HIP file into the scene. This means
               that any registered `hou.hipFile` event callbacks will be triggered
               with the `hou.hipFileEventType.BeforeMerge` and
               `hou.hipFileEventType.AfterMerge` events.

               @note This method loads a HIP file, completely overwriting
               everything that already exists in the scene. Therefore, any HAPI ids
               (node ids, part ids, etc.) that were obtained before calling this
               method will be invalidated.

       @ingroup HipFiles

       @param[in]      session
                       The session of Houdini you are interacting with.
                       See @ref HAPI_Sessions for more on sessions.
                       Pass NULL to just use the default in-process session.
                       <!-- default NULL -->

       @param[in]      file_name
                       Absolute path to the .hip file to load.

       @param[in]      cook_on_load
                       Set to true if you wish the nodes to cook as soon
                       as they are created. Otherwise, you will have to
                       call ::HAPI_CookNode() explicitly for each after you
                 call this function.
                 <!-- default false -->
    """
    result = HAPI_LIB.HAPI_LoadHIPFile(
        byref(session), c_char_p(hipname.encode('utf-8')), c_bool(cook_on_load))
    assert result == HDATA.Result.SUCCESS, \
        "LoadHipFile Failed with {0}".format(HDATA.Result(result).name)


def get_cook_options():
    """Get defalut cook option

    Returns:
        CookOptions: defalut cook option
    """
    cook_options = HDATA.CookOptions()
    cook_options.splitGeosByGroup = True
    cook_options.splitGeosByAttribute = False
    cook_options.splitAttrSH = 0
    cook_options.splitPointsByVertexAttributes = False
    cook_options.cookTemplatedGeos = True
    cook_options.maxVerticesPerPrimitive = 3
    cook_options.refineCurveToLinear = True
    cook_options.curveRefineLOD = 8
    cook_options.packedPrimInstancingMode = 2
    cook_options.handleBoxPartTypes = False
    cook_options.handleSpherePartTypes = False
    cook_options.preferOutputNodes = False
    return cook_options


def _get_string_buf_length(session: HDATA.Session,
                           string_handle: int) \
        -> int:
    """Wrapper for HAPI_GetStringBufLength
    Gives back the string length of the string with the given handle.

    Args:
        session (Session): The session of Houdini you are interacting with.
        string_handle (int): string handler of querying string

    Returns:
        int: string char length
    """
    buffer_length = c_int32()
    result = HAPI_LIB.HAPI_GetStringBufLength(byref(session), c_int(string_handle), byref(buffer_length))
    assert result == HDATA.Result.SUCCESS, \
        "GetStringBufLength Failed with {0}".format(HDATA.Result(result).name)
    return buffer_length.value


def _get_string(session: HDATA.Session,
                string_handle: int,
                length: int) \
        -> str:
    """Wrapper for HAPI_GetString
    Gives back the string value of the string with the given handle.

    Args:
        session (Session): The session of Houdini you are interacting with.
        string_handle (int): string handler of querying string
        length (int): string char length

    Returns:
        str: string queried
    """
    buffers = create_string_buffer(length)
    result = HAPI_LIB.HAPI_GetString(byref(session), c_int(string_handle), buffers, c_int(length))
    assert result == HDATA.Result.SUCCESS, \
        "GetString Failed with {0}".format(HDATA.Result(result).name)
    return buffers.value.decode()


def get_string(session: HDATA.Session,
               string_handle: int) \
        -> str:
    """Get literal string from hengine's string handler

    Args:
        session (Session): The session of Houdini you are interacting with.
        string_handle (int): string handler of querying string

    Returns:
        str: string queried
    """
    buffer_length = _get_string_buf_length(session, string_handle)
    return _get_string(session, string_handle, buffer_length)


def get_composed_node_cook_result(session, node_id, verbosity=HDATA.StatusVerbosity.ALL):
    """
    kivlin. wrapper for HAPI_ComposeNodeCookResult and HAPI_GetComposedNodeCookResult
    Args:
        session:
        node_id:
        verbosity:
    Returns:
    """
    buffer_length = c_int32()
    result = HAPI_LIB.HAPI_ComposeNodeCookResult(byref(session), c_int(node_id), verbosity, byref(buffer_length))
    assert result == HDATA.Result.SUCCESS, \
        "HAPI_ComposeNodeCookResult Failed with {0}".format(
            HDATA.Result(result).name)
    buffers = create_string_buffer(buffer_length.value)
    result = HAPI_LIB.HAPI_GetComposedNodeCookResult(byref(session), buffers, buffer_length)
    assert result == HDATA.Result.SUCCESS, \
        "HAPI_GetComposedNodeCookResult Failed with {0}".format(
            HDATA.Result(result).name)
    return buffers.value.decode()


def _get_status_string(session: HDATA.Session,
                       status: HDATA.StatusType = HDATA.StatusType.COOK_RESULT,
                       verbosity: HDATA.StatusVerbosity = HDATA.StatusVerbosity.ERRORS) \
        -> str:
    """Get current status string
    Wrapper for HAPI_GetStatusString and HAPI_GetStatusStringBufLength

    Args:
        session (Session): The session of Houdini you are interacting with.
        status (StatusType, optional): StatusType querying. \
            Defaults to HDATA.StatusType.COOK_RESULT.
        verbosity (StatusVerbosity, optional): Preferred verbosity level. \
            Defaults to HDATA.StatusVerbosity.ERRORS.

    Returns:
        str: status string
    """
    buffer_length = c_int32()
    result = HAPI_LIB.HAPI_GetStatusStringBufLength(
        byref(session), status, verbosity, byref(buffer_length))
    assert result == HDATA.Result.SUCCESS, \
        "GetStatusStringBufLength Failed with {0}".format(
            HDATA.Result(result).name)
    buffers = create_string_buffer(buffer_length.value)
    result = HAPI_LIB.HAPI_GetStatusString(
        byref(session), status, buffers, buffer_length)
    assert result == HDATA.Result.SUCCESS, \
        "GetStatusString Failed with {0}".format(
            HDATA.Result(result).name)
    return buffers.value.decode()


def get_server_env_int(session: HDATA.Session,
                       variable_name: str) \
        -> int:
    ''' Wrapper for HAPI_SetServerEnvInt
    @brief  Set environment variable for the server process as an integer.

         Note that this may affect other sessions on the same server
         process. The session parameter is mainly there to identify the
         server process, not the specific session.

         For in-process sessions, this will affect the current process's
         environment.
    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      variable_name
                     Name of the environment variable.
        @param[in]      value
                     The integer value.
    '''
    value = c_int32()

    result = HAPI_LIB.HAPI_GetServerEnvString(
        byref(session), c_char_p(variable_name.encode('utf-8')), byref(value))
    assert result == HDATA.Result.SUCCESS, \
        "GetServerEnvString Failed with {0}".format(HDATA.Result(result).name)
    return value.value


def get_server_env_string(session: HDATA.Session,
                          variable_name: str) -> str:
    ''' Wrapper for HAPI_GetServerEnvString
    @brief  Get environment variable from the server process as a string.
    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      variable_name
                     Name of the environmnet variable.
        @param[out]     value
                     The HAPI_StringHandle pointer to return the value in.
    '''
    value = c_int32()

    result = HAPI_LIB.HAPI_GetServerEnvString(
        byref(session), c_char_p(variable_name.encode('utf-8')), byref(value))
    assert result == HDATA.Result.SUCCESS, \
        "GetServerEnvString Failed with {0}".format(HDATA.Result(result).name)
    return get_string(session, value.value)


def get_server_env_var_count(session: HDATA.Session) -> int:
    ''' Wrapper for HAPI_GetServerEnvVarCount
    @brief  Provides the number of environment variables that are in
         the server environment's process

        Note that ::HAPI_GetServerEnvVarList() should be called directly after
         this method, otherwise there is the possibility that the environment
         variable count of the server will have changed by the time that
         ::HAPI_GetServerEnvVarList() is called.

    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[out]     env_count
                     A pointer to an int to return the value in
    '''
    env_count = c_int32()
    result = HAPI_LIB.HAPI_GetServerEnvVarCount(byref(session), byref(env_count))
    assert result == HDATA.Result.SUCCESS, \
        "GetServerEnvVarCount Failed with {0}".format(HDATA.Result(result).name)
    return env_count.value


def get_server_env_var_list(session: HDATA.Session,
                            start: int,
                            length: int):
    ''' Wrapper for HAPI_GetServerEnvVarList
    @brief  Provides a list of all of the environment variables
         in the server's process
    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->

        @param[out]     values_array
                     An ::HAPI_StringHandle array at least the size of length

        @param[in]      start
                     First index of range. Must be at least @c 0 and at most
                     @c env_count - 1 where @c env_count is the count returned by
                     ::HAPI_GetServerEnvVarCount()
                     <!-- min 0 -->
                     <!-- max ::HAPI_GetServerEnvVarCount -->
                     <!-- default 0 -->

        @param[in]      length
                     Given @c env_count returned by ::HAPI_GetServerEnvVarCount(),
                     length should be at least @c 0 and at most <tt>env_count - start.</tt>
                     <!-- default 0 -->
    '''
    values_array = c_int32()

    result = HAPI_LIB.HAPI_GetServerEnvVarList(
        byref(session), byref(values_array), c_int(start), c_int(length))
    assert result == HDATA.Result.SUCCESS, \
        "GetServerEnvVarList Failed with {0}".format(HDATA.Result(result).name)
    data = [x.decode() for x in values_array[:length]]
    return data


def set_server_env_string(session: HDATA.Session,
                          variable_name: str,
                          value: str):
    ''' Wrapper for HAPI_SetServerEnvString
    @brief  Set environment variable for the server process as a string.
         Note that this may affect other sessions on the same server
         process. The session parameter is mainly there to identify the
         server process, not the specific session.

         For in-process sessions, this will affect the current process's
         environment.
    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      variable_name
                     Name of the environmnet variable.
        @param[in]      value
                     The string value.
    '''
    result = HAPI_LIB.HAPI_SetServerEnvString(
        byref(session),
        c_char_p(variable_name.encode('utf-8')),
        c_char_p(value.encode('utf-8')))
    assert result == HDATA.Result.SUCCESS, \
        "SetServerEnvString Failed with {0}".format(HDATA.Result(result).name)


def set_server_env_int(session: HDATA.Session,
                       variable_name: str,
                       value: int):
    ''' Wrapper for HAPI_SetServerEnvInt
    @brief  Set environment variable for the server process as an integer.

         Note that this may affect other sessions on the same server
         process. The session parameter is mainly there to identify the
         server process, not the specific session.

         For in-process sessions, this will affect the current process's
         environment.
    @ingroup Environment
        @param[in]      session
                     The session of Houdini you are interacting with.
                     See @ref HAPI_Sessions for more on sessions.
                     Pass NULL to just use the default in-process session.
                     <!-- default NULL -->
        @param[in]      variable_name
                     Name of the environment variable.
        @param[in]      value
                     The integer value.
    '''
    result = HAPI_LIB.HAPI_SetServerEnvInt(
        byref(session),
        c_char_p(variable_name.encode('utf-8')),
        c_int(value))
    assert result == HDATA.Result.SUCCESS, \
        "SetServerEnvInt Failed with {0}".format(HDATA.Result(result).name)


#############################################################
#          Functions for working with PDG/TOPS
#############################################################


def get_pdg_graph_contexts(session: HDATA.Session,
                           start: int,
                           length: int) \
        -> Tuple[Array, Array]:
    """Wrapper for HAPI_GetPDGGraphContexts. autogenerated @highyang

    Return an array of PDG graph context names and ids, the first count names will be returned. These ids can be used with HAPI_GetPDGEvents and HAPI_GetPDGState . The values of the names can be retrieved with HAPI_GetString .

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        start: First index of range. Must be at least 0 and at most context_count - 1 where context_count is the count returned by HAPI_GetPDGGraphContextsCount()
        length: Given num_contexts returned by HAPI_GetPDGGraphContextsCount() , length should be at least 0 and at most num_contexts - start.

    Returns:
        Array of context names stored as ::HAPI_StringHandle at least the size of length. These can be used with HAPI_GetString() and are valid until the next call to this function.
        Array of graph context ids at least the size of length.
    """
    context_names_array = (c_int * length)()
    context_id_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetPDGGraphContexts(byref(session), byref(context_names_array), byref(context_id_array),
                                               c_int(start), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetPDGGraphContexts Failed with {0}'.format(HDATA.Result(result).name)
    return context_names_array, context_id_array


def get_pdg_graph_contexts_count(session: HDATA.Session) \
        -> int:
    """Wrapper for HAPI_GetPDGGraphContextsCount. autogenerated @highyang

    Return the total number of PDG graph contexts found.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.

    Returns:
        Total PDG graph contexts count.
    """
    num_contexts = c_int()
    result = HAPI_LIB.HAPI_GetPDGGraphContextsCount(byref(session), byref(num_contexts))
    assert HDATA.Result.SUCCESS == result, 'GetPDGGraphContextsCount Failed with {0}'.format(HDATA.Result(result).name)
    return num_contexts.value


def get_pdg_graph_context_id(session: HDATA.Session,
                             top_node_id: int) \
        -> int:
    """Wrapper for HAPI_GetPDGGraphContextId. autogenerated @highyang

    Get the PDG graph context for the specified TOP node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        top_node_id: The id of the TOP node to query its graph context.

    Returns:
        The PDG graph context id.
    """
    context_id = c_int()
    result = HAPI_LIB.HAPI_GetPDGGraphContextId(byref(session), c_int(top_node_id), byref(context_id))
    assert HDATA.Result.SUCCESS == result, 'GetPDGGraphContextId Failed with {0}'.format(HDATA.Result(result).name)
    return context_id.value


def cook_pdg(session: HDATA.Session,
             cook_node_id: int,
             generate_only: int,
             blocking: int):
    """Wrapper for HAPI_CookPDG. autogenerated @highyang

    Starts a PDG cooking operation. This can be asynchronous. Progress can be checked with HAPI_GetPDGState() and HAPI_GetPDGState() . Events generated during this cook can be collected with HAPI_GetPDGEvents() . Any uncollected events will be discarded at the start of the cook.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        cook_node_id: The node id of a TOP node for the cook operation.
        generate_only: 1 means only static graph generation will done. 0 means a full graph cook. Generation is always blocking.
        blocking: 0 means return immediately and cooking will be done asynchronously. 1 means return when cooking completes.

    Returns:

    """
    result = HAPI_LIB.HAPI_CookPDG(byref(session), c_int(cook_node_id), c_int(generate_only), c_int(blocking))
    assert HDATA.Result.SUCCESS == result, 'CookPDG Failed with {0}'.format(HDATA.Result(result).name)


def cook_pdg_all_outputs(session: HDATA.Session,
                         cook_node_id: int,
                         generate_only: int,
                         blocking: int):
    """Wrapper for HAPI_CookPDGAllOutputs. autogenerated @highyang

    Starts a PDG cooking operation. This can be asynchronous. Progress can be checked with HAPI_GetPDGState() and HAPI_GetPDGState() . Events generated during this cook can be collected with HAPI_GetPDGEvents() . Any uncollected events will be discarded at the start of the cook.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        cook_node_id: The node id of a TOP node for the cook operation.
        generate_only: 1 means only static graph generation will done. 0 means a full graph cook. Generation is always blocking.
        blocking: 0 means return immediately and cooking will be done asynchronously. 1 means return when cooking completes.

    Returns:

    """
    result = HAPI_LIB.HAPI_CookPDGAllOutputs(byref(session), c_int(cook_node_id), c_int(generate_only), c_int(blocking))
    assert HDATA.Result.SUCCESS == result, 'CookPDGAllOutputs Failed with {0}'.format(HDATA.Result(result).name)


def get_pdg_events(session: HDATA.Session,
                   graph_context_id: int,
                   length: int) \
        -> Tuple[Array, int, int]:
    """Wrapper for HAPI_GetPDGEvents. autogenerated @highyang

    Returns PDG events that have been collected. Calling this function will remove those events from the queue. Events collection is restarted by calls to HAPI_CookPDG() .

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The id of the graph context
        length: The size of the buffer passed in.

    Returns:
        buffer of HAPI_PDG_EventInfo of size at least length.
        Number of events removed from queue and copied to buffer.
        Number of queued events remaining after this operation.
    """
    event_array = (HDATA.PDG_EventInfo * length)()
    event_count = c_int()
    remaining_events = c_int()
    result = HAPI_LIB.HAPI_GetPDGEvents(byref(session), c_int(graph_context_id), byref(event_array), c_int(length),
                                        byref(event_count), byref(remaining_events))
    assert HDATA.Result.SUCCESS == result, 'GetPDGEvents Failed with {0}'.format(HDATA.Result(result).name)
    return event_array, event_count.value, remaining_events.value


def get_pdg_state(session: HDATA.Session,
                  graph_context_id: int) \
        -> int:
    """Wrapper for HAPI_GetPDGState. autogenerated @highyang

    Gets the state of a PDG graph.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The graph context id

    Returns:
        One of ::HAPI_PDG_State.
    """
    pdg_state = c_int()
    result = HAPI_LIB.HAPI_GetPDGState(byref(session), c_int(graph_context_id), byref(pdg_state))
    assert HDATA.Result.SUCCESS == result, 'GetPDGState Failed with {0}'.format(HDATA.Result(result).name)
    return pdg_state.value


def create_workitem(session: HDATA.Session,
                     node_id: int,
                     name: str,
                     index: int) \
        -> int:
    """Wrapper for HAPI_CreateWorkItem. autogenerated @highyang

    Creates a new pending workitem for the given node. The workitem will not be submitted to the graph until it is committed with HAPI_CommitWorkItems() . The node is expected to be a generator type.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        name: The null-terminated name of the workitem. The name will be automatically suffixed to make it unique.
        index: The index of the workitem. The semantics of the index are user defined.

    Returns:
        The id of the pending workitem.
    """
    workitem_id = c_int()
    result = HAPI_LIB.HAPI_CreateWorkItem(byref(session), c_int(node_id), byref(workitem_id),
                                          c_char_p(name.encode('utf-8')), c_int(index))
    assert HDATA.Result.SUCCESS == result, 'CreateWorkitem Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_id.value


def get_workitem_info(session: HDATA.Session,
                       graph_context_id: int,
                       workitem_id: int) \
        -> HDATA.PDG_WorkItemInfo:
    """Wrapper for HAPI_GetWorkItemInfo. autogenerated @highyang

    Retrieves the info of a given workitem by id.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The graph context that the workitem is in.
        workitem_id: The id of the workitem.

    Returns:
        The returned HAPI_PDG_WorkItemInfo for the workitem. Note that the enclosed string handle is only valid until the next call to this function.
    """
    workitem_info = HDATA.PDG_WorkItemInfo()
    result = HAPI_LIB.HAPI_GetWorkItemInfo(byref(session), c_int(graph_context_id), c_int(workitem_id),
                                           byref(workitem_info))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemInfo Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_info


def set_workitem_int_data(session: HDATA.Session,
                           node_id: int,
                           workitem_id: int,
                           data_name: str,
                           values_array: npt.NDArray[int],
                           length: int):
    """Wrapper for HAPI_SetWorkItemIntData. autogenerated @highyang

    Adds integer data to a pending PDG workitem data member for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the pending workitem returned by HAPI_CreateWorkItem()
        data_name: null-terminated name of the data member
        values_array: array of integer values
        length: number of values to copy from values_array to the parameter

    Returns:

    """
    intP = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetWorkItemIntData(byref(session), c_int(node_id), c_int(workitem_id),
                                              c_char_p(data_name.encode('utf-8')),
                                              values_array.flatten().ctypes.data_as(intP), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'SetWorkitemIntData Failed with {0}'.format(HDATA.Result(result).name)


def set_workitem_float_data(session: HDATA.Session,
                             node_id: int,
                             workitem_id: int,
                             data_name: str,
                             values_array: npt.NDArray[float],
                             length: int):
    """Wrapper for HAPI_SetWorkItemFloatData. autogenerated @highyang

    Adds float data to a pending PDG workitem data member for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the pending workitem returned by HAPI_CreateWorkItem()
        data_name: null-terminated name of the workitem data member
        values_array: array of float values
        length: number of values to copy from values_array to the parameter

    Returns:

    """
    floatP = POINTER(c_float)
    result = HAPI_LIB.HAPI_SetWorkItemFloatData(byref(session), c_int(node_id), c_int(workitem_id),
                                                c_char_p(data_name.encode('utf-8')),
                                                values_array.flatten().ctypes.data_as(floatP), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'SetWorkitemFloatData Failed with {0}'.format(HDATA.Result(result).name)


def set_workitem_string_data(session: HDATA.Session,
                              node_id: int,
                              workitem_id: int,
                              data_name: str,
                              data_index: int,
                              value: str):
    """Wrapper for HAPI_SetWorkItemStringData. autogenerated @highyang

    Adds integer data to a pending PDG workitem data member for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the created workitem returned by HAPI_CreateWorkItem()
        data_name: null-terminated name of the data member
        data_index: index of the string data member
        value: null-terminated string to copy to the workitem data member

    Returns:

    """
    result = HAPI_LIB.HAPI_SetWorkItemStringData(byref(session), c_int(node_id), c_int(workitem_id),
                                                 c_char_p(data_name.encode('utf-8')), c_int(data_index),
                                                 c_char_p(value.encode('utf-8')))
    assert HDATA.Result.SUCCESS == result, 'SetWorkitemStringData Failed with {0}'.format(HDATA.Result(result).name)


def commit_workitems(session: HDATA.Session,
                      node_id: int):
    """Wrapper for HAPI_CommitWorkItems. autogenerated @highyang

    Commits any pending workitems.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id for which the pending workitems have been created but not yet injected.

    Returns:

    """
    result = HAPI_LIB.HAPI_CommitWorkItems(byref(session), c_int(node_id))
    assert HDATA.Result.SUCCESS == result, 'CommitWorkitems Failed with {0}'.format(HDATA.Result(result).name)


def get_num_workitems(session: HDATA.Session,
                       node_id: int) \
        -> int:
    """Wrapper for HAPI_GetNumWorkItems. autogenerated @highyang

    Gets the number of workitems that are available on the given node. Should be used with HAPI_GetWorkItems .

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.

    Returns:
        The number of workitems.
    """
    num = c_int()
    result = HAPI_LIB.HAPI_GetNumWorkItems(byref(session), c_int(node_id), byref(num))
    assert HDATA.Result.SUCCESS == result, 'GetNumWorkitems Failed with {0}'.format(HDATA.Result(result).name)
    return num.value


def get_workitems(session: HDATA.Session,
                   node_id: int,
                   length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItems. autogenerated @highyang

    Gets the list of work item ids for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        length: The length of the workitem_ids buffer

    Returns:
        buffer for resulting array of ::HAPI_PDG_WorkItemId
    """
    workitem_ids_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItems(byref(session), c_int(node_id), byref(workitem_ids_array), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitems Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_ids_array


def get_workitem_data_length(session: HDATA.Session,
                              node_id: int,
                              workitem_id: int,
                              data_name: str) \
        -> int:
    """Wrapper for HAPI_GetWorkItemDataLength. autogenerated @highyang

    Gets the length of the workitem data member. It is the length of the array of data.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        data_name: null-terminated name of the data member

    Returns:
        The length of the data member array
    """
    length = c_int()
    result = HAPI_LIB.HAPI_GetWorkItemDataLength(byref(session), c_int(node_id), c_int(workitem_id),
                                                 c_char_p(data_name.encode('utf-8')), byref(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemDataLength Failed with {0}'.format(HDATA.Result(result).name)
    return length.value


def get_workitem_int_data(session: HDATA.Session,
                           node_id: int,
                           workitem_id: int,
                           data_name: str,
                           length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemIntData. autogenerated @highyang

    Gets int data from a work item member.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        data_name: null-terminated name of the data member
        length: The length of data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by HAPI_GetWorkItemDataLength() .
    """
    data_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItemIntData(byref(session), c_int(node_id), c_int(workitem_id),
                                              c_char_p(data_name.encode('utf-8')), byref(data_array), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemIntData Failed with {0}'.format(HDATA.Result(result).name)
    return data_array


def get_workitem_float_data(session: HDATA.Session,
                             node_id: int,
                             workitem_id: int,
                             data_name: str,
                             length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemFloatData. autogenerated @highyang

    Gets float data from a work item member.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        data_name: null-terminated name of the data member
        length: The length of the data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by HAPI_GetWorkItemDataLength() .
    """
    data_array = (c_float * length)()
    result = HAPI_LIB.HAPI_GetWorkItemFloatData(byref(session), c_int(node_id), c_int(workitem_id),
                                                c_char_p(data_name.encode('utf-8')), byref(data_array), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemFloatData Failed with {0}'.format(HDATA.Result(result).name)
    return data_array


def get_workitem_string_data(session: HDATA.Session,
                              node_id: int,
                              workitem_id: int,
                              data_name: str,
                              length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemStringData. autogenerated @highyang

    Gets string ids from a work item member.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        data_name: null-terminated name of the data member
        length: The length of data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by HAPI_GetWorkItemDataLength() . The data is an array of ::HAPI_StringHandle which can be used with HAPI_GetString() . The string handles are valid until the next call to this function.
    """
    data_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItemStringData(byref(session), c_int(node_id), c_int(workitem_id),
                                                 c_char_p(data_name.encode('utf-8')), byref(data_array), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemStringData Failed with {0}'.format(HDATA.Result(result).name)
    return data_array


def get_workitem_result_info(session: HDATA.Session,
                              node_id: int,
                              workitem_id: int,
                              resultinfo_count: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemResultInfo. autogenerated @highyang

    Gets the info for workitem results. The number of workitem results is found on the HAPI_PDG_WorkItemInfo returned by HAPI_GetWorkItemInfo()

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        resultinfo_count: The length of resultinfo_array

    Returns:
        Buffer to fill with info structs. String handles are valid until the next call of this function.
    """
    resultinfo_array = (HDATA.PDG_WorkItemOutputFile * resultinfo_count)()
    result = HAPI_LIB.HAPI_GetWorkItemResultInfo(byref(session), c_int(node_id), c_int(workitem_id), byref(resultinfo_array), c_int(resultinfo_count))
    assert HDATA.Result.SUCCESS == result, 'GetWorkitemResultInfo Failed with {0}'.format(HDATA.Result(result).name)
    return resultinfo_array


def create_workitem(session: HDATA.Session,
                     node_id: int,
                     name: str,
                     index: int) \
        -> int:
    """Wrapper for HAPI_CreateWorkItem. autogenerated @highyang

    Creates a new pending work item for the given node. The work item will not be submitted to the graph until it is committed with HAPI_CommitWorkItems() . The node is expected to be a generator type.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        name: The null-terminated name of the work item. The name will be automatically suffixed to make it unique.
        index: The index of the work item. The semantics of the index are user defined.

    Returns:
        The id of the pending workitem.
    """
    workitem_id = c_int()
    result = HAPI_LIB.HAPI_CreateWorkItem(byref(session), c_int(node_id), byref(workitem_id),
                                          c_char_p(name.encode('utf-8')), c_int(index))
    assert HDATA.Result.SUCCESS == result, 'CreateWorkItem Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_id.value


def get_workitem_info(session: HDATA.Session,
                       graph_context_id: int,
                       workitem_id: int) \
        -> HDATA.PDG_WorkItemInfo:
    """Wrapper for HAPI_GetWorkItemInfo. autogenerated @highyang

    Retrieves the info of a given work item by id.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The graph context that the work item is in.
        workitem_id: The id of the work item.

    Returns:
        The returned HAPI_PDG_WorkItemInfo for the work item. Note that the enclosed string handle is only valid until the next call to this function.
    """
    workitem_info = HDATA.PDG_WorkItemInfo()
    result = HAPI_LIB.HAPI_GetWorkItemInfo(byref(session), c_int(graph_context_id), c_int(workitem_id),
                                           byref(workitem_info))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemInfo Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_info


def set_workitem_int_attribute(session: HDATA.Session,
                                node_id: int,
                                workitem_id: int,
                                attribute_name: str,
                                values_array: npt.NDArray[int],
                                length: int):
    """Wrapper for HAPI_SetWorkItemIntAttribute. autogenerated @highyang

    Adds integer data to a pending PDG work item attribute for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the pending work item returned by HAPI_CreateWorkItem()
        attribute_name: null-terminated name of the work item attribute
        values_array: array of integer values
        length: number of values to copy from values_array to the parameter

    Returns:

    """
    intP = POINTER(c_int)
    result = HAPI_LIB.HAPI_SetWorkItemIntAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                   c_char_p(attribute_name.encode('utf-8')),
                                                   values_array.flatten().ctypes.data_as(intP), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'SetWorkItemIntAttribute Failed with {0}'.format(HDATA.Result(result).name)


def set_workitem_float_attribute(session: HDATA.Session,
                                  node_id: int,
                                  workitem_id: int,
                                  attribute_name: str,
                                  values_array: npt.NDArray[float],
                                  length: int):
    """Wrapper for HAPI_SetWorkItemFloatAttribute. autogenerated @highyang

    Adds float data to a pending PDG work item attribute for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the pending work item returned by HAPI_CreateWorkItem()
        attribute_name: null-terminated name of the work item attribute
        values_array: array of float values
        length: number of values to copy from values_array to the parameter

    Returns:

    """
    floatP = POINTER(c_float)
    result = HAPI_LIB.HAPI_SetWorkItemFloatAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                     c_char_p(attribute_name.encode('utf-8')),
                                                     values_array.flatten().ctypes.data_as(floatP), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'SetWorkItemFloatAttribute Failed with {0}'.format(HDATA.Result(result).name)


def set_workitem_string_attribute(session: HDATA.Session,
                                   node_id: int,
                                   workitem_id: int,
                                   attribute_name: str,
                                   data_index: int,
                                   value: str):
    """Wrapper for HAPI_SetWorkItemStringAttribute. autogenerated @highyang

    Adds integer data to a pending PDG work item attribute for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the created work item returned by HAPI_CreateWorkItem()
        attribute_name: null-terminated name of the work item attribute
        data_index: index of the string data member
        value: null-terminated string to copy to the work item data member

    Returns:

    """
    result = HAPI_LIB.HAPI_SetWorkItemStringAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                      c_char_p(attribute_name.encode('utf-8')), c_int(data_index),
                                                      c_char_p(value.encode('utf-8')))
    assert HDATA.Result.SUCCESS == result, 'SetWorkItemStringAttribute Failed with {0}'.format(
        HDATA.Result(result).name)


def commit_workitems(session: HDATA.Session,
                      node_id: int):
    """Wrapper for HAPI_CommitWorkItems. autogenerated @highyang

    Commits any pending work items.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id for which the pending work items have been created but not yet injected.

    Returns:

    """
    result = HAPI_LIB.HAPI_CommitWorkItems(byref(session), c_int(node_id))
    assert HDATA.Result.SUCCESS == result, 'CommitWorkItems Failed with {0}'.format(HDATA.Result(result).name)


def get_num_workitems(session: HDATA.Session,
                       node_id: int) \
        -> int:
    """Wrapper for HAPI_GetNumWorkItems. autogenerated @highyang

    Gets the number of work items that are available on the given node. Should be used with HAPI_GetWorkItems .

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.

    Returns:
        The number of work items.
    """
    num = c_int()
    result = HAPI_LIB.HAPI_GetNumWorkItems(byref(session), c_int(node_id), byref(num))
    assert HDATA.Result.SUCCESS == result, 'GetNumWorkItems Failed with {0}'.format(HDATA.Result(result).name)
    return num.value


def get_workitems(session: HDATA.Session,
                   node_id: int,
                   length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItems. autogenerated @highyang

    Gets the list of work item ids for the given node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        length: The length of the workitem_ids buffer

    Returns:
        buffer for resulting array of ::HAPI_PDG_WorkItemId
    """
    workitem_ids_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItems(byref(session), c_int(node_id), byref(workitem_ids_array), c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItems Failed with {0}'.format(HDATA.Result(result).name)
    return workitem_ids_array


def get_workitem_attribute_size(session: HDATA.Session,
                                 node_id: int,
                                 workitem_id: int,
                                 attribute_name: str) \
        -> int:
    """Wrapper for HAPI_GetWorkItemAttributeSize. autogenerated @highyang

    Gets the size of the work item attribute. It is the length of the array of data.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the work item
        attribute_name: null-terminated name of the work item attribute

    Returns:
        The length of the data member array
    """
    length = c_int()
    result = HAPI_LIB.HAPI_GetWorkItemAttributeSize(byref(session), c_int(node_id), c_int(workitem_id),
                                                    c_char_p(attribute_name.encode('utf-8')), byref(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemAttributeSize Failed with {0}'.format(HDATA.Result(result).name)
    return length.value


def get_workitem_int_attribute(session: HDATA.Session,
                                node_id: int,
                                workitem_id: int,
                                attribute_name: str,
                                length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemIntAttribute. autogenerated @highyang

    Gets int data from a work item attribute.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        attribute_name: null-terminated name of the work item attribute
        length: The length of data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by ::HAPI_GetWorkItemDataLength().
    """
    data_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItemIntAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                   c_char_p(attribute_name.encode('utf-8')), byref(data_array),
                                                   c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemIntAttribute Failed with {0}'.format(HDATA.Result(result).name)
    return data_array


def get_workitem_float_attribute(session: HDATA.Session,
                                  node_id: int,
                                  workitem_id: int,
                                  attribute_name: str,
                                  length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemFloatAttribute. autogenerated @highyang

    Gets float data from a work item attribute.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the workitem
        attribute_name: null-terminated name of the work item attribute
        length: The length of the data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by ::HAPI_GetWorkItemDataLength().
    """
    data_array = (c_float * length)()
    result = HAPI_LIB.HAPI_GetWorkItemFloatAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                     c_char_p(attribute_name.encode('utf-8')), byref(data_array),
                                                     c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemFloatAttribute Failed with {0}'.format(HDATA.Result(result).name)
    return data_array


def get_workitem_string_attribute(session: HDATA.Session,
                                   node_id: int,
                                   workitem_id: int,
                                   attribute_name: str,
                                   length: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemStringAttribute. autogenerated @highyang

    Gets string ids from a work item attribute.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the work item
        attribute_name: null-terminated name of the work item attribute
        length: The length of data_array

    Returns:
        buffer of at least size length to copy the data into. The required length should be determined by ::HAPI_GetWorkItemDataLength(). The data is an array of ::HAPI_StringHandle which can be used with HAPI_GetString() . The string handles are valid until the next call to this function.
    """
    data_array = (c_int * length)()
    result = HAPI_LIB.HAPI_GetWorkItemStringAttribute(byref(session), c_int(node_id), c_int(workitem_id),
                                                      c_char_p(attribute_name.encode('utf-8')), byref(data_array),
                                                      c_int(length))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemStringAttribute Failed with {0}'.format(
        HDATA.Result(result).name)
    return data_array


def get_workitem_output_files(session: HDATA.Session,
                               node_id: int,
                               workitem_id: int,
                               resultinfo_count: int) \
        -> Array:
    """Wrapper for HAPI_GetWorkItemOutputFiles. autogenerated @highyang

    Gets the info for work item output files. The number of work item results is found on the HAPI_PDG_WorkItemInfo returned by HAPI_GetWorkItemInfo()

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        workitem_id: The id of the work item
        resultinfo_count: The length of resultinfo_array

    Returns:
        Buffer to fill with info structs. String handles are valid until the next call of this function.
    """
    resultinfo_array = (HDATA.PDG_WorkItemOutputFile * resultinfo_count)()
    result = HAPI_LIB.HAPI_GetWorkItemOutputFiles(byref(session), c_int(node_id), c_int(workitem_id),
                                                  byref(resultinfo_array), c_int(resultinfo_count))
    assert HDATA.Result.SUCCESS == result, 'GetWorkItemOutputFiles Failed with {0}'.format(HDATA.Result(result).name)
    return resultinfo_array


def dirty_pdg_node(session: HDATA.Session,
                   node_id: int,
                   clean_results: bool):
    """Wrapper for HAPI_DirtyPDGNode. autogenerated @highyang

    Dirties the given node. Cancels the cook if necessary and then deletes all workitems on the node.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        node_id: The node id.
        clean_results: Remove the results generated by the node.

    Returns:

    """
    result = HAPI_LIB.HAPI_DirtyPDGNode(byref(session), c_int(node_id), c_bool(clean_results))
    assert HDATA.Result.SUCCESS == result, 'DirtyPDGNode Failed with {0}'.format(HDATA.Result(result).name)


def pause_pdg_cook(session: HDATA.Session,
                   graph_context_id: int):
    """Wrapper for HAPI_PausePDGCook. autogenerated @highyang

    Pause the PDG cooking operation.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The id of the graph context

    Returns:

    """
    result = HAPI_LIB.HAPI_PausePDGCook(byref(session), c_int(graph_context_id))
    assert HDATA.Result.SUCCESS == result, 'PausePDGCook Failed with {0}'.format(HDATA.Result(result).name)


def cancel_pdg_cook(session: HDATA.Session,
                    graph_context_id: int):
    """Wrapper for HAPI_CancelPDGCook. autogenerated @highyang

    Cancel the PDG cooking operation.

    Args:
        session: The session of Houdini you are interacting with. See HAPI_Sessions for more on sessions. Pass NULL to just use the default in-process session.
        graph_context_id: The id of the graph context

    Returns:

    """
    result = HAPI_LIB.HAPI_CancelPDGCook(byref(session), c_int(graph_context_id))
    assert HDATA.Result.SUCCESS == result, 'CancelPDGCook Failed with {0}'.format(HDATA.Result(result).name)


"""
 todo : add other apis: 
   HAPI_GetPDGGraphContextsCount
   HAPI_CookPDGAllOutputs
   HAPI_CreateWorkItem
   HAPI_GetWorkItemInfo
   HAPI_SetWorkItemIntAttribute
   HAPI_SetWorkItemFloatAttribute
   HAPI_SetWorkItemStringAttribute
   HAPI_CommitWorkItems
   HAPI_GetNumWorkItems
   HAPI_GetWorkItems
   HAPI_GetWorkItemAttributeSize
   HAPI_GetWorkItemIntAttribute
   HAPI_GetWorkItemFloatAttribute
   HAPI_GetWorkItemOutputFiles
   
   HAPI_PDG_WorkItemId will replace HAPI_PDG_WorkitemId
"""
