/* Copyright (c) 1997-1999 Miller Puckette.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* interface objects */

#include "m_pd.h"
#include "m_imp.h"
#include "g_canvas.h"
#include "s_stuff.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/* we need the following for [pdinfo] ... */

#define MAXNDEV 20
#define DEVDESCSIZE 80

/* -------------------------- print ------------------------------ */
t_class *print_class;

typedef struct _print
{
    t_object x_obj;
    t_symbol *x_sym;
} t_print;

static void *print_new(t_symbol *sel, int argc, t_atom *argv)
{
    t_print *x = (t_print *)pd_new(print_class);
    if (argc == 0)
        x->x_sym = gensym("print");
    else if (argc == 1 && argv->a_type == A_SYMBOL)
    {
        t_symbol *s = atom_getsymbolarg(0, argc, argv);
        if (!strcmp(s->s_name, "-n"))
            x->x_sym = &s_;
        else x->x_sym = s;
    }
    else
    {
        int bufsize;
        char *buf;
        t_binbuf *bb = binbuf_new();
        binbuf_add(bb, argc, argv);
        binbuf_gettext(bb, &buf, &bufsize);
        buf = resizebytes(buf, bufsize, bufsize+1);
        buf[bufsize] = 0;
        x->x_sym = gensym(buf);
        freebytes(buf, bufsize+1);
        binbuf_free(bb);
    }
    return (x);
}

static void print_bang(t_print *x)
{
    post("%s: bang", x->x_sym->s_name);
}

static void print_pointer(t_print *x, t_gpointer *gp)
{
    post("%s: (gpointer)", x->x_sym->s_name);
}

static void print_float(t_print *x, t_float f)
{
    post("%s: %g", x->x_sym->s_name, f);
}

static void print_list(t_print *x, t_symbol *s, int argc, t_atom *argv)
{
    if (argc && argv->a_type != A_SYMBOL)
        startpost("%s%s%g", x->x_sym->s_name,
            (*x->x_sym->s_name ? ": " : ""),
            atom_getfloatarg(0, argc--, argv++));
    else startpost("%s: %s", x->x_sym->s_name,
        (argc > 1 ? s_list.s_name : (argc == 1 ? s_symbol.s_name :
            s_bang.s_name)));
    postatom(argc, argv);
    endpost();
}

static void print_anything(t_print *x, t_symbol *s, int argc, t_atom *argv)
{
    startpost("%s: %s", x->x_sym->s_name, s->s_name);
    postatom(argc, argv);
    endpost();
}

static void print_setup(void)
{
    print_class = class_new(gensym("print"), (t_newmethod)print_new, 0,
        sizeof(t_print), 0, A_GIMME, 0);
    class_addbang(print_class, print_bang);
    class_addfloat(print_class, print_float);
    class_addpointer(print_class, print_pointer);
    class_addlist(print_class, print_list);
    class_addanything(print_class, print_anything);
}

/* --------------pdinfo, canvasinfo, classinfo, objectinfo --------------- */
static t_class *canvasinfo_class;

typedef struct _canvasinfo {
    t_object x_obj;
    t_canvas *x_canvas;
    t_float   x_depth;
} t_canvasinfo;

static t_class *pdinfo_class;

typedef struct _pdinfo {
    t_object x_obj;
} t_pdinfo;

static t_class *classinfo_class;

typedef struct _classinfo {
    t_object x_obj;
    t_outlet *x_out2;
    t_symbol *x_name;
} t_classinfo;

static t_class *objectinfo_class;

typedef struct _objectinfo {
    t_object x_obj;
    t_outlet *x_out2;
    t_gpointer x_gp;
    t_canvas *x_canvas;
} t_objectinfo;

/* used by all the *info objects */

static int info_to_console = 0;

void info_out(t_text *te, t_symbol *s, int argc, t_atom *argv)
{
    if (info_to_console)
    {
        startpost("%s:", s->s_name);
        if (argc > 0)
            postatom(argc, argv);
        endpost();
    }
    else
    {
        outlet_list(te->ob_outlet,
            &s_list, argc, argv);
    }
}

void info_print(t_text *te)
{
    t_class *c = classtable_findbyname(te->ob_pd->c_name);
    if(c == NULL)
    {
        pd_error(te, "s: can't find entry in classtable");
        return;
    }
    info_to_console = 1;
    t_int i;
    t_methodentry *m;
    for(i = c->c_nmethod, m = c->c_methods; i; i--, m++)
        if(m->me_name != gensym("print"))
            (m->me_fun)(te, m->me_name, 0, 0);
    /* not sure why this doesn't work... */
    /* pd_forwardmess(te->te_pd, 0, 0); */
    info_to_console = 0;
}

/* -------------------------- canvasinfo ------------------------------ */
/* climb up to a parent canvas */
t_canvas *canvas_climb(t_canvas *c, int level)
{
  if(level <= 0)
      return c;
  else
  {
      t_canvas *ret = c;
      while(level && ret->gl_owner)
      {
        ret = ret->gl_owner;
        level--;
      }
      return ret;
  }
}

void canvas_getargs_after_creation(t_canvas *c, int *argcp, t_atom **argvp);

void canvasinfo_args(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    int n = 0;
    t_atom *a = 0;
    t_binbuf *b;
    if(!c) return;
    if (s == gensym("args")) c = canvas_getrootfor(c);
    b = c->gl_obj.te_binbuf;
    if(!b)
    {
        info_out((t_text *)x, s, 0, 0);
    }
    else
    {
        if (s == gensym("args"))
        {
            canvas_getargs_after_creation(c, &n, &a);
            info_out((t_text *)x, s, n, a);
        }
        else
        {
            /* For "boxtext" have to escape semi, comma, dollar, and
               dollsym atoms, which is what binbuf_addbinbuf does.
               Otherwise the user could pass them around or save them
               unescaped, which might cause trouble. */
            t_binbuf *escaped = binbuf_new();
            binbuf_addbinbuf(escaped, b);
            n = binbuf_getnatom(escaped);
            a = binbuf_getvec(escaped);
            info_out((t_text *)x, s, n, a);
            binbuf_free(escaped);
        }
    }
}

void canvasinfo_coords(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_int gop = c->gl_isgraph + c->gl_hidetext;
    t_atom at[9];
    SETFLOAT(at, (c->gl_isgraph) ? c->gl_x1 : 0);
    SETFLOAT(at+1, (c->gl_isgraph) ? c->gl_y1 : 0);
    SETFLOAT(at+2, (c->gl_isgraph) ? c->gl_x2 : 0);
    SETFLOAT(at+3, (c->gl_isgraph) ? c->gl_y2 : 0);
    SETFLOAT(at+4, (c->gl_isgraph) ? c->gl_pixwidth : 0);
    SETFLOAT(at+5, (c->gl_isgraph) ? c->gl_pixheight : 0);
    SETFLOAT(at+6, (c->gl_isgraph) ? gop : 0);
    SETFLOAT(at+7, (c->gl_isgraph) ? c->gl_xmargin : 0);
    SETFLOAT(at+8, (c->gl_isgraph) ? c->gl_ymargin : 0);
    info_out((t_text *)x, s, 9, at);
}

void canvasinfo_dir(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    c = canvas_getrootfor(c);
    t_atom at[1];
    SETSYMBOL(at, canvas_getdir(c));
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_dirty(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[1];
    SETFLOAT(at, c->gl_dirty);
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_dollarzero(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    c = canvas_getrootfor(c);
    t_symbol *d = gensym("$0");
    t_symbol *ret = canvas_realizedollar(c, d);
    float f = (float)strtod(ret->s_name,NULL);
    t_atom at[1];
    SETFLOAT(at, f);
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_editmode(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[1];
    SETFLOAT(at, c->gl_edit);
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_filename(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    c = canvas_getrootfor(c);
    t_atom at[1];
    SETSYMBOL(at, c->gl_name);
    info_out((t_text *)x, s, 1, at);
}

int binbuf_match(t_binbuf *inbuf, t_binbuf *searchbuf, int wholeword);

void canvasinfo_find(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    int i, match = 0;
    t_atom at[1], *ap;
    t_gpointer *gp = (t_gpointer *)t_getbytes(500 * sizeof(*gp));
    t_gobj *y;
    t_binbuf *searchbuf = binbuf_new();
    t_binbuf *outbuf = binbuf_new();
    binbuf_add(searchbuf, argc, argv);
    for (y = c->gl_list; y && match < 500; y = y->g_next)
    {
        t_binbuf *objbuf;
        /* if it's not t_object (e.g., a scalar), or if it is but
           does not have any binbuf content, send a bang... */
        if (pd_checkobject(&y->g_pd) &&
            (objbuf = ((t_text *)y)->te_binbuf) &&
            binbuf_match(objbuf, searchbuf, 1))
        {
            match++;
            gpointer_init(gp+match-1);
            gpointer_setglist(gp+match-1, c, y);
            SETPOINTER(at, gp+match-1);
            binbuf_add(outbuf, 1, at); 
        }
    }
    if (match >= 500)
        post("canvasinfo: warning: find is currently limited to 500 results. "
             "Truncating the output to 500 elements...");
    info_out((t_text *)x, s, binbuf_getnatom(outbuf), binbuf_getvec(outbuf));
    for (i = 0, ap = binbuf_getvec(outbuf); i < binbuf_getnatom(outbuf); i++)
    {
        t_gpointer *gp = (ap+i)->a_w.w_gpointer;
        gpointer_unset(gp);
    }
    binbuf_free(outbuf);
    binbuf_free(searchbuf);
    freebytes(gp, 500 * sizeof(*gp));
}

void canvasinfo_gobjs(t_canvasinfo *x, t_float xpos, t_float ypos,
    int all)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    int x1, y1, x2, y2, i, atom_count = 0;
    /* hack to avoid memory allocation. Maybe there's a way to use the
       XLIST_ATOMS_ALLOCA macro? */
    t_atom at[500];
    t_gpointer *gp, *gvec;
    gp = gvec = (t_gpointer *)t_getbytes(500 * sizeof (*gvec));
    t_gobj *y;
    for (y = c->gl_list, i = 0; y && atom_count < 500; y = y->g_next, i++)
    {
        if (all || canvas_hitbox(c, y, xpos, ypos, &x1, &y1, &x2, &y2))
        {
            gpointer_init(gp);
            gpointer_setglist(gp, c, y);
            SETPOINTER(at+atom_count, gp);
            atom_count++;
            gp++;
        }
    }
    if (atom_count >= 500)
        post("canvasinfo: warning: hitbox is currently limited to 500 objects. "
             "Truncating the output to 500 elements...");
    info_out((t_text *)x, gensym("hitbox"), atom_count, at);
    for (i = 0, gp = gvec; i < atom_count; i++, gp++)
        gpointer_unset(gp);
    freebytes(gvec, 500 * sizeof(*gvec));
}

void canvasinfo_bang(t_canvasinfo *x)
{
    canvasinfo_gobjs(x, 0, 0, 1);
}

void canvasinfo_hitbox(t_canvasinfo *x, t_floatarg xpos, t_floatarg ypos)
{
    canvasinfo_gobjs(x, xpos, ypos, 0);
}

void canvasinfo_name(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    char buf[MAXPDSTRING];
    snprintf(buf, MAXPDSTRING, ".x%lx", (long unsigned int)c);
    t_atom at[1];
    SETSYMBOL(at, gensym(buf));
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_pointer(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[1];
    t_gpointer gp;
    gpointer_init(&gp);
    gpointer_setglist(&gp, c, 0);
    SETPOINTER(at, &gp);
    info_out((t_text *)x, s, 1, at);
    gpointer_unset(&gp);
}

void canvasinfo_posonparent(t_canvasinfo *x, t_symbol *s,
    int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[2];
    SETFLOAT(at, c->gl_obj.te_xpix);
    SETFLOAT(at+1, c->gl_obj.te_ypix);
    info_out((t_text *)x, s, 2, at);
}

void canvasinfo_screenpos(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[4];
    SETFLOAT(at, c->gl_screenx1);
    SETFLOAT(at+1, c->gl_screeny1);
    SETFLOAT(at+2, c->gl_screenx2);
    SETFLOAT(at+3, c->gl_screeny2);
    info_out((t_text *)x, s, 4, at);
}

void canvasinfo_toplevel(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_float f = c->gl_owner ? 0 : 1;
    t_atom at[1];
    SETFLOAT(at, f);
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_vis(t_canvasinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_atom at[1];
    SETFLOAT(at, glist_isvisible(c));
    info_out((t_text *)x, s, 1, at);
}

void canvasinfo_print(t_canvasinfo *x)
{
    info_print((t_text *)x);
}

void *canvasinfo_new(t_floatarg f)
{
    t_canvasinfo *x = (t_canvasinfo *)pd_new(canvasinfo_class);
    t_glist *glist = (t_glist *)canvas_getcurrent();
    t_canvas *c = (t_canvas*)glist_getcanvas(glist);
    x->x_canvas = c;
    floatinlet_new(&x->x_obj, &x->x_depth);
    outlet_new(&x->x_obj, &s_list);
    x->x_depth = f;
    return (void *)x;
}

void canvasinfo_setup(void)
{
    canvasinfo_class = class_new(gensym("canvasinfo"),
        (t_newmethod)canvasinfo_new, 0,
        sizeof(t_canvasinfo),
        CLASS_DEFAULT, A_DEFFLOAT, 0);

    class_addbang(canvasinfo_class, canvasinfo_bang);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_args,
        gensym("args"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_args,
        gensym("boxtext"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_coords,
        gensym("coords"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_dir,
        gensym("dir"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_dirty,
        gensym("dirty"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_dollarzero,
        gensym("dollarzero"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_editmode,
        gensym("editmode"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_filename,
        gensym("filename"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_find,
        gensym("find"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_hitbox,
        gensym("hitbox"), A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_name,
        gensym("name"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_pointer,
        gensym("pointer"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_posonparent,
        gensym("posonparent"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_screenpos,
        gensym("screenpos"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_toplevel,
        gensym("toplevel"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_vis,
        gensym("vis"), A_GIMME, 0);
    class_addmethod(canvasinfo_class, (t_method)canvasinfo_print,
        gensym("print"), 0);

    post("canvasinfo: v0.1");
    post("stable canvasinfo methods: args dir dirty editmode vis");

}

/* -------------------------- pdinfo ------------------------------ */
static t_class *pdinfo_class;

t_symbol *getapiname(int id)
{
    t_symbol *s = 0;
    switch (id)
    {
        case API_NONE: s = gensym("none"); break;
        case API_ALSA: s = gensym("ALSA"); break;
        case API_OSS: s = gensym("OSS"); break;
        case API_MMIO: s = gensym("MMIO"); break;
        case API_PORTAUDIO: s = gensym("PortAudio"); break;
        case API_JACK: s = gensym("JACK"); break;
        case API_SGI: s = gensym("SGI"); break;
        //case API_AUDIOUNIT: s = gensym("AudioUnit"); break;
        //case API_ESD: s = gensym("ESD"); break;
        //case API_DUMMY: s = gensym("dummy"); break;
    }
    return s;
}

void pdinfo_dir(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    t_symbol *dir = pd_getdirname();
    if (!dir)
    {
        pd_error(x, "pdinfo: can't find pd's directory");
        return;
    }
    SETSYMBOL(at, dir);
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_dsp(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    SETFLOAT(at, (t_float)canvas_dspstate);
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_audio_api(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    t_symbol *api = getapiname(sys_audioapi);
    SETSYMBOL(at, api);
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_canvaslist(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_canvas *c;
    int j, i = 0;
    t_binbuf *outbuf = binbuf_new();
    t_atom at[1];
    for (c = canvas_list; c; c = c->gl_next)
        i++;
    t_gpointer *gp = (t_gpointer *)t_getbytes(i * sizeof(*gp));
    for (c = canvas_list, i = 0; c; c = c->gl_next, i++)
    {
        gpointer_init(gp+i);
        gpointer_setglist(gp+i, c, 0);
        SETPOINTER(at, gp+i);
        binbuf_add(outbuf, 1, at); 
    }
    info_out((t_text *)x, s, binbuf_getnatom(outbuf), binbuf_getvec(outbuf));
    binbuf_free(outbuf);
    for (j = 0; j < i; j++)
        gpointer_unset(gp+j);
    freebytes(gp, i * sizeof(*gp));
}

/* maybe this should be the bang method? */
void pdinfo_classlist(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    int size = classtable_size();
    if (info_to_console)
    {
        t_atom at[2];
        SETFLOAT(at, size);
        SETSYMBOL(at+1, gensym("classes loaded (\"classtable\" outputs "
                               "the full list)"));
        info_out((t_text *)x, s, 2, at);
    }
    else
    {
        t_atom at[size];
        classtable_tovec(size, at);
        info_out((t_text *)x, s, size, at);
    }
}

void pdinfo_audioin(t_pdinfo *x, t_symbol *s, int argc, t_atom *arg)
{
//        char i
}

void pdinfo_audio_api_list_all(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[7];
    int i;
    for(i = 0; i < 7; i++)
        SETSYMBOL(at+i, getapiname(i));
    info_out((t_text *)x, s, i, at);
}

void pdinfo_audio_apilist(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[8];
    int n = 0;
#ifdef USEAPI_OSS
    SETSYMBOL(at+n, getapiname(API_OSS)); n++;
#endif
#ifdef USEAPI_MMIO
    SETSYMBOL(at+n, getapiname(API_MMIO)); n++;
#endif
#ifdef USEAPI_ALSA
    SETSYMBOL(at+n, getapiname(API_ALSA)); n++;
#endif
#ifdef USEAPI_PORTAUDIO
#ifdef MSW
    SETSYMBOL(at+n, getapiname(API_PORTAUDIO));
#else
#ifdef OSX
    SETSYMBOL(at+n, getapiname(API_PORTAUDIO));
#else
    SETSYMBOL(at+n, getapiname(API_PORTAUDIO));
#endif
#endif
    n++;
#endif
#ifdef USEAPI_JACK
    SETSYMBOL(at+n, getapiname(API_JACK)); n++;
#endif
#ifdef USEAPI_AUDIOUNIT
    SETSYMBOL(at+n, getapiname(API_AUDIOUNIT)); n++;
#endif
#ifdef USEAPI_ESD
    SETSYMBOL(at+n, getapiname(API_ESD)); n++;
#endif
#ifdef USEAPI_DUMMY
    SETSYMBOL(at+n, getapiname(API_DUMMY)); n++;
#endif
    info_out((t_text *)x, s, n, at);
}

void pdinfo_audio_listdevs(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    char indevlist[MAXNDEV*DEVDESCSIZE], outdevlist[MAXNDEV*DEVDESCSIZE];
    int nindevs = 0, noutdevs = 0, i, canmulti = 0, cancallback = 0;
    sys_get_audio_devs(indevlist, &nindevs,
            outdevlist, &noutdevs,
            &canmulti, &cancallback,
            MAXNDEV, DEVDESCSIZE);
    t_atom at[4];
    if (s == gensym("audio-multidev-support"))
    {
        SETFLOAT(at, canmulti);
        info_out((t_text *)x, s, 1, at); 
    }
    else if (s == gensym("audio-indevlist"))
    {
        for (i = 0; i < nindevs; i++)
           SETSYMBOL(at+i, gensym(indevlist + i * DEVDESCSIZE));
        info_out((t_text *)x, s, i, at);
    }
    else if (s == gensym("audio-outdevlist"))
    {
        for (i = 0; i < noutdevs; i++)
           SETSYMBOL(at+i, gensym(outdevlist + i * DEVDESCSIZE));
        info_out((t_text *)x, s, i, at);
    }
}

void pdinfo_audio_dev(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    int devno;
    if (argc) devno = (int)atom_getfloatarg(0, argc, argv);
    else devno = 0;
    int naudioindev, audioindev[MAXAUDIOINDEV], chindev[MAXAUDIOINDEV];
    int naudiooutdev, audiooutdev[MAXAUDIOOUTDEV], choutdev[MAXAUDIOOUTDEV];
    int rate, advance, callback;
    sys_get_audio_params(&naudioindev, audioindev, chindev,
        &naudiooutdev, audiooutdev, choutdev, &rate, &advance, &callback);
    int *dev, *chan, ndev;
    if (s == gensym("audio-indev"))
        dev = audioindev, chan = chindev, ndev = naudioindev;
    else
        dev = audiooutdev, chan = choutdev, ndev = naudiooutdev;
    if (devno >= 0 && devno < ndev)
    {
        t_atom at[2];
        SETFLOAT(at, (t_float)dev[devno]);
        SETFLOAT(at+1, (t_float)chan[devno]);
        info_out((t_text *)x, s, 2, at);
    }
    else
        info_out((t_text *)x, s, 0, 0);
}

void pdinfo_midi_api(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    t_symbol *api, *def = gensym("DEFAULT");
#ifdef USEAPI_OSS
    def = gensym("OSS");
#endif
    api = sys_midiapi ? gensym("ALSA") : def;
    SETSYMBOL(at, api);
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_midi_apilist(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[8];
    int n = 0;
    SETSYMBOL(at+n, gensym("DEFAULT"));
#ifdef USEAPI_OSS
    SETSYMBOL(at+n, gensym("OSS"));
#endif
    n++;
#ifdef USEAPI_ALSA
    SETSYMBOL(at+n, getapiname(API_ALSA)); n++;
#endif
    info_out((t_text *)x, s, n, at);
}

void pdinfo_midi_listdevs(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    char indevlist[MAXMIDIINDEV*DEVDESCSIZE],
        outdevlist[MAXMIDIOUTDEV*DEVDESCSIZE];
    int nindevs = 0, noutdevs = 0, i;
    sys_get_midi_devs(indevlist, &nindevs,
            outdevlist, &noutdevs,
            MAXNDEV, DEVDESCSIZE);
    t_atom at[4];
    if (s == gensym("midi-indevlist"))
    {
        for (i = 0; i < nindevs; i++)
           SETSYMBOL(at+i, gensym(indevlist + i * DEVDESCSIZE));
        info_out((t_text *)x, s, i, at);
    }
    else if (s == gensym("midi-outdevlist"))
    {
        for (i = 0; i < noutdevs; i++)
           SETSYMBOL(at+i, gensym(outdevlist + i * DEVDESCSIZE));
        info_out((t_text *)x, s, i, at);
    }
}

void pdinfo_midi_dev(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    int devno, nmidiindev, midiindev[MAXMIDIINDEV],
        nmidioutdev, midioutdev[MAXMIDIOUTDEV];
    int *dev, ndev;
    if (argc) devno = (int)atom_getfloatarg(0, argc, argv);
    else devno = 0;
    sys_get_midi_params(&nmidiindev, midiindev, &nmidioutdev, midioutdev);
    if (s == gensym("midi-indev"))
        dev = midiindev, ndev = nmidiindev;
    else
        dev = midioutdev, ndev = nmidioutdev;
    if (devno >= 0 && devno < ndev)
    {
        t_atom at[1];
        SETFLOAT(at, (t_float)dev[devno]);
        info_out((t_text *)x, s, 1, at);
    }
    else
        info_out((t_text *)x, s, 0, 0);
}

void pdinfo_audio_outdev(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    int devno;
    if (argc) devno = (int)atom_getfloatarg(0, argc, argv);
    else devno = 0;
    int naudioindev, audioindev[MAXAUDIOINDEV], chindev[MAXAUDIOINDEV];
    int naudiooutdev, audiooutdev[MAXAUDIOOUTDEV], choutdev[MAXAUDIOOUTDEV];
    int rate, advance, callback;
    sys_get_audio_params(&naudioindev, audioindev, chindev,
        &naudiooutdev, audiooutdev, choutdev, &rate, &advance, &callback);
    if (devno >= 0 && devno < naudioindev)
    {
        t_atom at[2];
        SETFLOAT(at, (t_float)audioindev[devno]);
        SETFLOAT(at+1, (t_float)chindev[devno]);
        info_out((t_text *)x, s, 2, at);
    }
    else
        info_out((t_text *)x, s, 0, 0);
}

void pdinfo_audio_inchannels(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    SETFLOAT(at, (t_float)sys_get_inchannels());
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_audio_outchannels(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    SETFLOAT(at, (t_float)sys_get_outchannels());
    info_out((t_text *)x, s, 1, at);
}


void pdinfo_audio_samplerate(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    SETFLOAT(at, (t_float)sys_getsr());
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_audio_blocksize(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_atom at[1];
    SETFLOAT(at, (t_float)sys_getblksize());
    info_out((t_text *)x, s, 1, at);
}

void pdinfo_version(t_pdinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    int major=0, minor=0, bugfix=0;
    sys_getversion(&major, &minor, &bugfix);
    t_atom at[3];
    SETFLOAT(at, (t_float)major);
    SETFLOAT(at+1, (t_float)minor);
    SETFLOAT(at+2, (t_float)bugfix);
    info_out((t_text *)x, s, 3, at);
}

void pdinfo_print(t_pdinfo *x)
{
    info_print((t_text *)x);
}

void *pdinfo_new(t_symbol *s, t_int argc, t_atom *argv)
{
    t_pdinfo *x = (t_pdinfo *)pd_new(pdinfo_class);
    outlet_new(&x->x_obj, &s_list);    
    return (void *)x;
}

void pdinfo_setup(void)
{
    pdinfo_class = class_new(gensym("pdinfo"),
        (t_newmethod)pdinfo_new, 0,
        sizeof(t_pdinfo),
        CLASS_DEFAULT, 0);

    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_api,
        gensym("audio-api"), A_DEFFLOAT, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_apilist,
        gensym("audio-apilist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_api_list_all,
        gensym("audio-apilist-all"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_inchannels,
        gensym("audio-inchannels"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_dev,
        gensym("audio-indev"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_listdevs,
        gensym("audio-indevlist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_listdevs,
        gensym("audio-multidev-support"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_outchannels,
        gensym("audio-outchannels"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_dev,
        gensym("audio-outdev"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_listdevs,
        gensym("audio-outdevlist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_blocksize,
        gensym("blocksize"), A_GIMME, 0);
    /* this needs a better name-- the user doesn't have to know the
       name used in the implementation */
    class_addmethod(pdinfo_class, (t_method)pdinfo_canvaslist,
        gensym("canvaslist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_classlist,
        gensym("classlist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_dir,
        gensym("dir"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_dsp,
        gensym("dsp-status"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_api,
        gensym("midi-api"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_apilist,
        gensym("midi-apilist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_dev,
        gensym("midi-indev"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_listdevs,
        gensym("midi-indevlist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_dev,
        gensym("midi-outdev"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_midi_listdevs,
        gensym("midi-outdevlist"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_audio_samplerate,
        gensym("samplerate"), A_GIMME, 0);
    class_addmethod(pdinfo_class, (t_method)pdinfo_version,
        gensym("version"), A_GIMME, 0);

    class_addmethod(pdinfo_class, (t_method)pdinfo_print,
        gensym("print"), 0);

    post("pdinfo: v.0.1");
    post("stable pdinfo methods: dir dsp version");
}

/* -------------------------- classinfo ------------------------------ */
t_symbol *attosym(t_atomtype at)
{
    t_symbol *s;
    switch (at)
    {
        case A_FLOAT: s = gensym("A_FLOAT"); break;
        case A_SYMBOL: s = gensym("A_SYMBOL"); break;
        case A_POINTER: s = gensym("A_POINTER"); break;
        case A_DEFFLOAT: s = gensym("A_DEFFLOAT"); break;
        case A_DEFSYM: s = gensym("A_DEFSYM"); break;
        case A_GIMME: s = gensym("A_GIMME"); break;
        case A_CANT: s = gensym("A_CANT"); break;
        default: s = 0;
    }
    return s;
}

void classinfo_args(t_classinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_class *c;
    if(!(c = classtable_findbyname(x->x_name)))
    {
        outlet_bang(x->x_out2);
        return;
    }
    c = classtable_findbyname(gensym("objectmaker"));
    if (!c)
    {
        pd_error(x, "classinfo: no objectmaker.");
        return;
    }
    t_atom ap[MAXPDARG];
    t_int i;
    t_methodentry *m;
    for(i = c->c_nmethod, m = c->c_methods; i; i--, m++)
    {
        if(m->me_name == x->x_name)
            break;
    }
    /* We have to check if there was a match-- while the class
       table holds all classes, the objectmaker only creates a
       new method entry if the class has a t_newmethod defined
       in its setup routine. This misses a few objects such as 
       gatom which can't be typed into a box. Consequently I'm
       sending a bang to the reject outlet for those objects--
       unfortunately that means a "reject" bang could mean the
       object either doesn't exist, or it doesn't have its own
       t_newmethod. However, one can use the other [classinfo]
       methods to unambiguously check the class for existence.
    */
    if (i)
    {
        t_atomtype arg, *args = m->me_arg;
        for(i = 0; arg = *args; args++, i++)
        {
            t_symbol *sym = attosym(arg);
            if (!sym)
            {
                pd_error(x, "classinfo: %s: bad argtype", x->x_name->s_name);
                return;
            }
            SETSYMBOL(ap+i, sym);
        }
        info_out((t_text *)x, s, i, ap);
    }
    else
        outlet_bang(x->x_out2);
}

void classinfo_externdir(t_classinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_class *c;
    if(!(c = classtable_findbyname(x->x_name)))
    {
        outlet_bang(x->x_out2);
        return;
    }
    t_atom at[1];
    SETSYMBOL(at, c->c_externdir);
    info_out((t_text *)x, s, 1, at);
}

void classinfo_methods(t_classinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_class *c;
    if(!(c = classtable_findbyname(x->x_name)))
    {
        outlet_bang(x->x_out2);
        return;
    }
    t_atom at[1];
    SETFLOAT(at, c->c_nmethod);
    info_out((t_text *)x, s, 1, at);
}

void classinfo_size(t_classinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_class *c;
    if(!(c = classtable_findbyname(x->x_name)))
    {
        outlet_bang(x->x_out2);
        return;
    }
    t_atom at[1];
    SETFLOAT(at, (t_float)c->c_size);
    info_out((t_text *)x, s, 1, at);
} 
 
void classinfo_float(t_classinfo *x, t_float f)
{
    t_class *c;
    if(c = classtable_findbyname(x->x_name))
    {
        if(f >= 0 && (t_int)f < c->c_nmethod)
        {
            t_atom ap[MAXPDARG+1];
            if(c->c_nmethod)
            {
                t_methodentry *me = c->c_methods;
                SETSYMBOL(ap, (me+(t_int)f)->me_name);
                t_atomtype arg, *args = (me+(t_int)f)->me_arg;
                t_int n;
                for(n = 1; arg = *args; args++, n++)
                {
                    t_symbol *s = attosym(arg);
                    SETSYMBOL(ap+n, s);
                }
                info_out((t_text *)x, &s_list, n, ap);
            }
        }
        else
            info_out((t_text *)x, &s_bang, 0, 0);
    }
    else
        outlet_bang(x->x_out2);
}

void classinfo_print(t_classinfo *x)
{
    info_print((t_text *)x);
}

void *classinfo_new(t_symbol *s)
{
    t_classinfo *x = (t_classinfo *)pd_new(classinfo_class);
    x->x_name = s;
    symbolinlet_new(&x->x_obj, &x->x_name);
    outlet_new(&x->x_obj, &s_anything);
    x->x_out2 = outlet_new(&x->x_obj, &s_bang);
    return (void *)x;
}

void classinfo_setup(void)
{
    classinfo_class = class_new(gensym("classinfo"),
        (t_newmethod)classinfo_new, 0,
        sizeof(t_classinfo),
        CLASS_DEFAULT, A_DEFSYM, 0);

    class_addfloat(classinfo_class, classinfo_float);
    class_addmethod(classinfo_class, (t_method)classinfo_args, gensym("args"),
        A_GIMME, 0);
    class_addmethod(classinfo_class, (t_method)classinfo_externdir,
        gensym("externdir"), A_GIMME, 0);
    class_addmethod(classinfo_class, (t_method)classinfo_methods,
        gensym("methods"), A_GIMME, 0);
    class_addmethod(classinfo_class, (t_method)classinfo_size, gensym("size"),
        A_GIMME, 0);
    class_addmethod(classinfo_class, (t_method)classinfo_print,
        gensym("print"), 0);

    post("classinfo: v.0.1");
    post("stable classinfo methods: size");

/*
   todo: make an objectinfo class to get the kind of info that canvasinfo "hitbox"
   currently gives
*/
}

/* -------------------------- objectinfo ------------------------------ */

int gpointer_check_gobj(const t_gpointer *gp);

t_gobj *objectinfo_getobject(t_objectinfo *x)
{
//    if (gpointer_check_gobj(&x->x_gp))
//        post("we passed the check in getobject");
    /* needs to pass the check AND point to a gobj */
    if (gpointer_check_gobj(&x->x_gp) && x->x_gp.gp_stub->gs_which == GP_GLIST)
    {
//        post("we passed total check");
        return x->x_gp.gp_un.gp_gobj;
    }
    else
        return 0;
}

void objectinfo_bang(t_objectinfo *x)
{
    t_atom at[1];
    t_gpointer gp;
    gpointer_init(&gp);
    gpointer_setglist(&gp, x->x_canvas, (t_gobj *)x);
//    if (gpointer_check_gobj(&gp))
//        post("creating pointer passed the check");
//    else
//        post("didn't create right");
    SETPOINTER(at, &gp);
    info_out((t_text *)x, &s_pointer, 1, at);
    gpointer_unset(&gp);
}

void objectinfo_float(t_objectinfo *x, t_floatarg f)
{
    /*
    t_canvas *c = canvas_climb(x->x_canvas, x->x_depth);
    t_gobj *obj = objectinfo_getobject(x);
    post("object is .%x", obj);
    x->x_test = obj;
    */
}

void objectinfo_parseargs(t_objectinfo *x, int argc, t_atom *argv)
{
    /* probably don't need to specify index number as an argument */
    /*
    if (argc)
    {
        if (argv->a_type == A_FLOAT)
        {
            x->x_index = atom_getfloatarg(0, argc, argv);
        }
        else
        {
            pd_error(x, "expected float but didn't get float");
        }
        argc--;
        argv++;
    }
    */
    /* another stopgap comment out...
    if (argc)
    {
        if (argv->a_type == A_FLOAT)
            x->x_index = atom_getfloatarg(0, argc, argv);
        else
            pd_error(x, "expected float but didn't get a float");
    }
    */
}

void objectinfo_boxtext(t_objectinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_gobj *ob = objectinfo_getobject(x);
    if (ob)
    {
//        post("it's an obj");
        int n = 0;
        t_atom *a = 0;
        t_binbuf *b;
        /* if it's not t_object (e.g., a scalar), or if it is but
           does not have any binbuf content, send a bang... */
        if (!pd_checkobject(&ob->g_pd) ||
            !(b = ((t_text *)ob)->te_binbuf))
        {
            info_out((t_text *)x, &s_bang, 0, 0);
        }
        else
        {
            /* We have to escape semi, comma, dollar, and dollsym atoms,
               which is what binbuf_addbinbuf does.  Otherwise the user
               could pass them around or save them unescaped, which might 
               cause trouble. */
            t_binbuf *escaped = binbuf_new();
            binbuf_addbinbuf(escaped, b);
            n = binbuf_getnatom(escaped);
            a = binbuf_getvec(escaped);
            info_out((t_text *)x, s, n, a);
            binbuf_free(escaped);
        }
    }
    else
        outlet_bang(x->x_out2);
}

void objectinfo_bbox(t_objectinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_gobj *ob = objectinfo_getobject(x);
    int x1, y1, x2, y2;
    if(ob)
    {
      /* check for a getrectfn */
        if (ob->g_pd->c_wb && ob->g_pd->c_wb->w_getrectfn)
        {
            t_atom at[4];
            /* objectinfo_getobject will only return a gobj* if the gstub
               is a GP_GLIST, so we can safely fetch the glist from our
               gpointer.  Not sure if gobj can ever be inside an array, but
               if so I'm excluding those cases here... */
            t_canvas *c = x->x_gp.gp_stub->gs_un.gs_glist;
            gobj_getrect(ob, c, &x1, &y1, &x2, &y2);
            SETFLOAT(at, (t_float)x1);
            SETFLOAT(at+1, (t_float)y1);
            SETFLOAT(at+2, (t_float)x2);
            SETFLOAT(at+3, (t_float)y2);
            info_out((t_text *)x, s, 4, at);
        }
        else
        {
            info_out((t_text *)x, &s_bang, 0, 0);
        }
    }
    else
        outlet_bang(x->x_out2);
}

void objectinfo_classname(t_objectinfo *x, t_symbol *s,
    int argc, t_atom *argv)
{
    t_gobj *ob = objectinfo_getobject(x);
    t_atom at[1];
    if(ob)
    {
        char *classname = class_getname(ob->g_pd);
        SETSYMBOL(at, gensym(classname));
        info_out((t_text *)x, s, 1, at);
    }
    else
        outlet_bang(x->x_out2);
}

void objectinfo_index(t_objectinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_gobj *ob = objectinfo_getobject(x);
    if(ob)
    {
        t_atom at[4];
        t_gobj *y;
        int i;
        /* objectinfo_getobject will only return a gobj* if the gstub
           is a GP_GLIST, so we can safely fetch the glist from our
           gpointer.  Not sure if gobj can ever be inside an array, but
           if so I'm excluding those cases here... */
        t_canvas *c = x->x_gp.gp_stub->gs_un.gs_glist;
        for (i = 0, y = c->gl_list; y; y = y->g_next, i++)
        {
            if (y == ob)
            {
                SETFLOAT(at, (t_float)i);
                info_out((t_text *)x, s, 1, at);
                return;
            }
        }
        info_out((t_text *)x, s, 0, at);
    }
    else
        outlet_bang(x->x_out2);
}

void objectinfo_xlets(t_objectinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    t_gobj *ob = objectinfo_getobject(x);
    t_atom at[1];
    if(ob)
    {
        /* we exclude scalars here because they are not patchable */
        if (pd_class(&ob->g_pd) != scalar_class)
        {
//            post("not a scalar...");
            t_object *o = (t_object *)ob;
            int n = (s == gensym("inlets") ? obj_ninlets(o) : obj_noutlets(o));
            SETFLOAT(at, (t_float)n);
            info_out((t_text *)x, s, 1, at);
        }
    }
    else
        outlet_bang(x->x_out2);
}

void objectinfo_print(t_objectinfo *x, t_symbol *s, int argc, t_atom *argv)
{
    objectinfo_parseargs(x, argc, argv);
    info_print((t_text *)x);
}

void *objectinfo_new(t_floatarg f)
{
    t_objectinfo *x = (t_objectinfo *)pd_new(objectinfo_class);
    x->x_canvas = canvas_getcurrent();
    pointerinlet_new(&x->x_obj, &x->x_gp);
    outlet_new(&x->x_obj, &s_anything);
    x->x_out2 = outlet_new(&x->x_obj, &s_bang);
    return (void *)x;
}

void objectinfo_setup(void)
{
    objectinfo_class = class_new(gensym("objectinfo"),
        (t_newmethod)objectinfo_new, 0,
        sizeof(t_objectinfo),
        CLASS_DEFAULT, A_DEFFLOAT, 0);

    class_addbang(objectinfo_class, objectinfo_bang);
    class_addfloat(objectinfo_class, objectinfo_float);
    class_addmethod(objectinfo_class, (t_method)objectinfo_bbox,
        gensym("bbox"), A_GIMME, 0);
    class_addmethod(objectinfo_class, (t_method)objectinfo_boxtext,
        gensym("boxtext"), A_GIMME, 0);
    class_addmethod(objectinfo_class, (t_method)objectinfo_classname,
        gensym("class"), A_GIMME, 0);
    class_addmethod(objectinfo_class, (t_method)objectinfo_index,
        gensym("index"), A_GIMME, 0);
    class_addmethod(objectinfo_class, (t_method)objectinfo_xlets,
        gensym("inlets"), A_GIMME, 0);
    class_addmethod(objectinfo_class, (t_method)objectinfo_xlets,
        gensym("outlets"), A_GIMME, 0);


    class_addmethod(objectinfo_class, (t_method)objectinfo_print,
        gensym("print"), A_GIMME, 0);

    post("objectinfo: v.0.1");
    post("stable objectinfo methods: class");
}

void x_interface_setup(void)
{
    print_setup();
    canvasinfo_setup();
    pdinfo_setup();
    classinfo_setup();
    objectinfo_setup();
}