#include "win32ole.h"

struct olevariabledata {
    ITypeInfo *pTypeInfo;
    UINT index;
};

static void olevariable_free(void *ptr);
static size_t olevariable_size(const void *ptr);
static VALUE folevariable_name(VALUE self);
static VALUE ole_variable_ole_type(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_ole_type(VALUE self);
static VALUE ole_variable_ole_type_detail(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_ole_type_detail(VALUE self);
static VALUE ole_variable_value(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_value(VALUE self);
static VALUE ole_variable_visible(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_visible(VALUE self);
static VALUE ole_variable_kind(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_variable_kind(VALUE self);
static VALUE ole_variable_varkind(ITypeInfo *pTypeInfo, UINT var_index);
static VALUE folevariable_varkind(VALUE self);
static VALUE folevariable_inspect(VALUE self);

static const rb_data_type_t olevariable_datatype = {
    "win32ole_variable",
    {NULL, olevariable_free, olevariable_size,},
    0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};

static void
olevariable_free(void *ptr)
{
    struct olevariabledata *polevar = ptr;
    OLE_FREE(polevar->pTypeInfo);
    free(polevar);
}

static size_t
olevariable_size(const void *ptr)
{
    return ptr ? sizeof(struct olevariabledata) : 0;
}

/*
 * Document-class: WIN32OLE::Variable
 *
 *   +WIN32OLE::Variable+ objects represent OLE variable information.
 */

VALUE
create_win32ole_variable(ITypeInfo *pTypeInfo, UINT index, VALUE name)
{
    struct olevariabledata *pvar;
    VALUE obj = TypedData_Make_Struct(cWIN32OLE_VARIABLE, struct olevariabledata,
                                      &olevariable_datatype, pvar);
    pvar->pTypeInfo = pTypeInfo;
    OLE_ADDREF(pTypeInfo);
    pvar->index = index;
    rb_ivar_set(obj, rb_intern("name"), name);
    return obj;
}

/*
 *  call-seq:
 *     name
 *
 *  Returns the name of variable.
 *
 *     tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *     variables = tobj.variables
 *     variables.each do |variable|
 *       puts "#{variable.name}"
 *     end
 *
 *     The result of above script is following:
 *       xlChart
 *       xlDialogSheet
 *       xlExcel4IntlMacroSheet
 *       xlExcel4MacroSheet
 *       xlWorksheet
 *
 */
static VALUE
folevariable_name(VALUE self)
{
    return rb_ivar_get(self, rb_intern("name"));
}

static VALUE
ole_variable_ole_type(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE type;
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        ole_raise(hr, eWIN32OLERuntimeError, "failed to GetVarDesc");
    type = ole_typedesc2val(pTypeInfo, &(pVarDesc->elemdescVar.tdesc), Qnil);
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    return type;
}

/*
 *   call-seq:
 *      ole_type
 *
 *   Returns OLE type string.
 *
 *     tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *     variables = tobj.variables
 *     variables.each do |variable|
 *       puts "#{variable.ole_type} #{variable.name}"
 *     end
 *
 *     The result of above script is following:
 *       INT xlChart
 *       INT xlDialogSheet
 *       INT xlExcel4IntlMacroSheet
 *       INT xlExcel4MacroSheet
 *       INT xlWorksheet
 *
 */
static VALUE
folevariable_ole_type(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_ole_type(pvar->pTypeInfo, pvar->index);
}

static VALUE
ole_variable_ole_type_detail(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE type = rb_ary_new();
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        ole_raise(hr, eWIN32OLERuntimeError, "failed to GetVarDesc");
    ole_typedesc2val(pTypeInfo, &(pVarDesc->elemdescVar.tdesc), type);
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    return type;
}

/*
 *  call-seq:
 *     ole_type_detail
 *
 *  Returns detail information of type. The information is array of type.
 *
 *     tobj = WIN32OLE::Type.new('DirectX 7 for Visual Basic Type Library', 'D3DCLIPSTATUS')
 *     variable = tobj.variables.find {|variable| variable.name == 'lFlags'}
 *     tdetail  = variable.ole_type_detail
 *     p tdetail # => ["USERDEFINED", "CONST_D3DCLIPSTATUSFLAGS"]
 *
 */
static VALUE
folevariable_ole_type_detail(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_ole_type_detail(pvar->pTypeInfo, pvar->index);
}

static VALUE
ole_variable_value(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE val = Qnil;
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        return Qnil;
    if(pVarDesc->varkind == VAR_CONST)
        val = ole_variant2val(V_UNION1(pVarDesc, lpvarValue));
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    return val;
}

/*
 *  call-seq:
 *     value
 *
 *  Returns value if value is exists. If the value does not exist,
 *  this method returns nil.
 *
 *     tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *     variables = tobj.variables
 *     variables.each do |variable|
 *       puts "#{variable.name} #{variable.value}"
 *     end
 *
 *     The result of above script is following:
 *       xlChart = -4109
 *       xlDialogSheet = -4116
 *       xlExcel4IntlMacroSheet = 4
 *       xlExcel4MacroSheet = 3
 *       xlWorksheet = -4167
 *
 */
static VALUE
folevariable_value(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_value(pvar->pTypeInfo, pvar->index);
}

static VALUE
ole_variable_visible(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE visible = Qfalse;
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        return visible;
    if (!(pVarDesc->wVarFlags & (VARFLAG_FHIDDEN |
                                 VARFLAG_FRESTRICTED |
                                 VARFLAG_FNONBROWSABLE))) {
        visible = Qtrue;
    }
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    return visible;
}

/*
 *  call-seq:
 *     visible?
 *
 *  Returns true if the variable is public.
 *
 *     tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *     variables = tobj.variables
 *     variables.each do |variable|
 *       puts "#{variable.name} #{variable.visible?}"
 *     end
 *
 *     The result of above script is following:
 *       xlChart true
 *       xlDialogSheet true
 *       xlExcel4IntlMacroSheet true
 *       xlExcel4MacroSheet true
 *       xlWorksheet true
 *
 */
static VALUE
folevariable_visible(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_visible(pvar->pTypeInfo, pvar->index);
}

static VALUE
ole_variable_kind(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE kind = rb_str_new2("UNKNOWN");
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        return kind;
    switch(pVarDesc->varkind) {
    case VAR_PERINSTANCE:
        kind = rb_str_new2("PERINSTANCE");
        break;
    case VAR_STATIC:
        kind = rb_str_new2("STATIC");
        break;
    case VAR_CONST:
        kind = rb_str_new2("CONSTANT");
        break;
    case VAR_DISPATCH:
        kind = rb_str_new2("DISPATCH");
        break;
    default:
        break;
    }
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    return kind;
}

/*
 * call-seq:
 *   variable_kind
 *
 * Returns variable kind string.
 *
 *    tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *    variables = tobj.variables
 *    variables.each do |variable|
 *      puts "#{variable.name} #{variable.variable_kind}"
 *    end
 *
 *    The result of above script is following:
 *      xlChart CONSTANT
 *      xlDialogSheet CONSTANT
 *      xlExcel4IntlMacroSheet CONSTANT
 *      xlExcel4MacroSheet CONSTANT
 *      xlWorksheet CONSTANT
 */
static VALUE
folevariable_variable_kind(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_kind(pvar->pTypeInfo, pvar->index);
}

static VALUE
ole_variable_varkind(ITypeInfo *pTypeInfo, UINT var_index)
{
    VARDESC *pVarDesc;
    HRESULT hr;
    VALUE kind = Qnil;
    hr = pTypeInfo->lpVtbl->GetVarDesc(pTypeInfo, var_index, &pVarDesc);
    if (FAILED(hr))
        return kind;
    pTypeInfo->lpVtbl->ReleaseVarDesc(pTypeInfo, pVarDesc);
    kind = RB_INT2FIX(pVarDesc->varkind);
    return kind;
}

/*
 *  call-seq:
 *     varkind
 *
 *  Returns the number which represents variable kind.
 *    tobj = WIN32OLE::Type.new('Microsoft Excel 9.0 Object Library', 'XlSheetType')
 *    variables = tobj.variables
 *    variables.each do |variable|
 *      puts "#{variable.name} #{variable.varkind}"
 *    end
 *
 *    The result of above script is following:
 *       xlChart 2
 *       xlDialogSheet 2
 *       xlExcel4IntlMacroSheet 2
 *       xlExcel4MacroSheet 2
 *       xlWorksheet 2
 */
static VALUE
folevariable_varkind(VALUE self)
{
    struct olevariabledata *pvar;
    TypedData_Get_Struct(self, struct olevariabledata, &olevariable_datatype, pvar);
    return ole_variable_varkind(pvar->pTypeInfo, pvar->index);
}

/*
 *  call-seq:
 *     inspect -> String
 *
 *  Returns the OLE variable name and the value with class name.
 *
 */
static VALUE
folevariable_inspect(VALUE self)
{
    VALUE v = rb_inspect(folevariable_value(self));
    VALUE n = folevariable_name(self);
    VALUE detail = rb_sprintf("%"PRIsVALUE"=%"PRIsVALUE, n, v);
    return make_inspect("WIN32OLE::Variable", detail);
}

VALUE cWIN32OLE_VARIABLE;

void Init_win32ole_variable(void)
{
    cWIN32OLE_VARIABLE = rb_define_class_under(cWIN32OLE, "Variable", rb_cObject);
    /* Alias of WIN32OLE::Variable, for the backward compatibility */
    rb_define_const(rb_cObject, "WIN32OLE" "_VARIABLE", cWIN32OLE_VARIABLE);
    rb_deprecate_constant(rb_cObject, "WIN32OLE" "_VARIABLE");
    rb_undef_alloc_func(cWIN32OLE_VARIABLE);
    rb_define_method(cWIN32OLE_VARIABLE, "name", folevariable_name, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "ole_type", folevariable_ole_type, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "ole_type_detail", folevariable_ole_type_detail, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "value", folevariable_value, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "visible?", folevariable_visible, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "variable_kind", folevariable_variable_kind, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "varkind", folevariable_varkind, 0);
    rb_define_method(cWIN32OLE_VARIABLE, "inspect", folevariable_inspect, 0);
    rb_define_alias(cWIN32OLE_VARIABLE, "to_s", "name");
}
