/*
This source file is part of Scol
For the latest info, see http://www.scolring.org

Copyright (c) 2011 Stephane Bisaro, aka Iri <iri@irizone.net>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt

For others informations, please contact us from http://www.scolring.org/
*/

#include "main_tree.h"


/* PRIVATE */
static int ObjXmlTreeDestroy (mmachine m, int handsys, int mobj)
{
    s_xml s;
    int res;

    MMechostr(MSKDEBUG, "ObjXmlTreeDestroy : entering ...\n");

	/* return the object handle */
    s = (s_xml) MMfetch (m, MTOP (mobj), S_LIBTREE_HANDLE);
    if (s == NULL)
    {
        MMechostr(MSKDEBUG, "ObjXmlTreeDestroy : object already destroyed\n");
        return 0;
    }

	xmlFreeTextReader (s->reader);
	/* it's bad, it's ok */
	#if ((defined linux) || (defined __linux))
    res = pthread_cancel (s->sub->thread);
    if (res != 0)
        MMechostr (MSKDEBUG, "ObjXmlTreeDestroy : thread already canceled\n");
    #elif ((defined _WIN32) || (defined __WIN32__))
    res = TerminateThread (s->sub->thread, 0);
    if (res == 0)
        MMechostr (MSKDEBUG, "ObjXmlTreeDestroy : thread already canceled\n");
    #endif
    if (s->doc != NULL)
        xmlFreeDoc (s->doc);
    res = close (s->sub->file);
    if (res != 0)
        MMechostr (MSKDEBUG, "ObjXmlTreeDestroy : file descriptor not closed : %d : %s\n", errno, strerror (errno));
    free (s->sub);
    free (s);
	/* unregister this object to the Scol machine */
    MMstore (m, MTOP (mobj), S_LIBTREE_HANDLE, (int) NULL);
    MMechostr(MSKDEBUG, "ObjXmlTreeDestroy : object has been destroyed\n");
    return 0;
}



static void tree_xml2_create (mmachine m, int mobj, int msrc, int menc, int mflags, int flag)
{
    s_xml s;

    MMechostr (MSKDEBUG, "tree_xml2_create : enteting ...\n");

    s = malloc (sizeof (struct S_xml));
    if (s == NULL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_create error : not enough memory ...\n");
        MMpull (m);
        MMpush (m, NIL);
        return;
    }
    s->sub = malloc (sizeof (struct Subxml));

    s->sub->file = 0;
    s->sub->filename = NULL;
    s->sub->buffer = NULL;
    s->sub->enc_from = NULL;
    s->sub->enc_to = NULL;
    s->sub->sm = m;
    s->sub->flags = 0;

    if (mflags != NIL)
        s->sub->flags = MTOI (mflags);
    if (menc != NIL)
        s->sub->enc_to = MMstartstr (m, MTOP (menc));

    switch (flag)
    {
        case 0:   /* filename */
        case 1:   /* url */
            s->sub->filename = MMstartstr (m, MTOP (msrc));
            s->doc = xmlReadFile (s->sub->filename, s->sub->enc_to, s->sub->flags);
            break;
        case 2:   /* buffer / string in memory */
            s->sub->buffer = MMstartstr (m, MTOP (msrc));
            s->doc = xmlReadMemory (s->sub->buffer, strlen (s->sub->buffer), NULL, s->sub->enc_to, s->sub->flags);
            break;
        case 3:   /* file descriptor */
        {
            FILE *f;
            int fd, scoltypefile;

            scoltypefile = OBJtypebyname ("OBJTYPFILE"); /* retrieve the id of this Scol type */
            f = (FILE *) MMfetch (m, MTOP (mobj), scoltypefile);
            fd = fileno (f);
            if (fd == -1)
            {
                MMechostr (MSKDEBUG, "tree_xml2_create error : the file is descriptor is bad\n");
                MMpull (m);
                MMpush (m, NIL);
                return;
            }
            s->sub->file = fd;
            s->doc = xmlReadFd (s->sub->file, NULL, s->sub->enc_to, s->sub->flags);
            break;
        }
        default:
            MMechostr (MSKDEBUG, "tree_xml2_create error : i ignore the question !...\n");
            MMpull (m);
            MMpush (m, NIL);
            return;
    }

    if (s->doc == NULL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_create error : unable to parse these datas\n");
        MMpull (m);
        MMpush (m, NIL);
        return;
    }

    s_xml2create_object_tree (m, s);
    return;
}

static void tree_xml2_create_from_vm (mmachine m, int flag)
{
    int mchannel, msrc, menc, mflags;

    MMechostr (MSKDEBUG, "tree_xml2_create_from_vm : entering ...\n");

    mflags = MMpull (m);    /* xml2 options */
    menc = MMpull (m);      /* encodage */
    msrc = MMpull (m);      /* source : filename, url, buffer or FILE object */
    mchannel = MMget (m, 0);/* channel */

    if (mchannel == NIL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_create_from_vm error : the channel is nil !\n");
        MMpull (m);
        MMpush (m, NIL);
        return;
    }
    if (msrc == NIL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_create_from_vm error : the data or object is nil !\n");
        MMpull (m);
        MMpush (m, NIL);
        return;
    }

    tree_xml2_create (m, 0, msrc, menc, mflags, flag);
    return;
}

static int tree_xml2_read_node (mmachine m, xmlNodePtr parent, int cpt)
{
    xmlNodePtr node = NULL, attrvalue = NULL;
    xmlAttrPtr attr = NULL;
    int i, /*cpt = 0,*/ cpt1 = 0;

    if (parent == NULL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_read_node : parent NULL\n");
        /*MMpush (m, NIL);
        OBJcallreflex (m, 1);*/
        return cpt;
    }

    for (node = parent; node; node = node->next)
    {   MMechostr (MSKDEBUG, "tree_xml2_read_node : TOP\n");
        cpt1 = 0;
        if (node->type == XML_ELEMENT_NODE)
        {
            MMechostr (MSKDEBUG, "tree_xml2_read_node : name = %s\n", node->name);
            Mpushstrbloc (m, (char *) node->name);
            cpt++;

            for (attr = node->properties; attr; attr = attr->next)
            {
                MMpush (m, ITOM (TREEXML2_ATTRIBUTE));

                MMechostr (MSKDEBUG, "tree_xml2_read_node : attribute name = %s\n", attr->name);
                Mpushstrbloc (m, (char *) attr->name);

                attrvalue = attr->children;
                MMechostr (MSKDEBUG, "tree_xml2_read_node : attribute value = %s\n", attrvalue->content);
                Mpushstrbloc (m, (char *) attrvalue->content);

                MMpush (m, ITOM (3));
                MBdeftab (m);

                cpt1++;
            }

            MMechostr (MSKDEBUG, "tree_xml2_read_node : cpt = %d\n", cpt);
            cpt = tree_xml2_read_node (m, node->children, cpt);

            /*MMpush (m, NIL);
            for (i = 0; i < cpt2; i++)
            {
                MMpush (m, ITOM (2));
                MBdeftab (m);
            }

            cpt1++;*/
        }
        else if (node->type == XML_TEXT_NODE)
        {
            MMechostr (MSKDEBUG, "tree_xml2_read_node : TExT = %s\n", node->content);

            MMpush (m, ITOM (TREEXML2_TEXT));
            MMpush (m, NIL);
            Mpushstrbloc (m, (char *) node->content);

            MMpush (m, ITOM (3));
            MBdeftab (m);
            cpt1++;
        }
        else if (node->type == XML_CDATA_SECTION_NODE)
        {
            MMechostr (MSKDEBUG, "tree_xml2_read_node : CDaTA = %s\n", node->content);

            MMpush (m, ITOM (TREEXML2_CDATA));
            MMpush (m, NIL);
            Mpushstrbloc (m, (char *) node->content);

            MMpush (m, ITOM (3));
            MBdeftab (m);

            cpt1++;
        }
        else if (node->type == XML_COMMENT_NODE)
        {
            MMechostr (MSKDEBUG, "tree_xml2_read_node : COMmENT = %s\n", node->content);

            MMpush (m, ITOM (TREEXML2_COMMENT));
            MMpush (m, NIL);
            Mpushstrbloc (m, (char *) node->content);

            MMpush (m, ITOM (3));
            MBdeftab (m);

            cpt1++;
        }

        MMpush (m, NIL);
        for (i = 0; i < cpt1; i++)
        {
            MMpush (m, ITOM (2));
            MBdeftab (m);
        }   /* [[I S S] r1] */
MMechostr (MSKDEBUG, "tree_xml2_read_node : cpt1 = %d\n", cpt1);
        MMpush (m, ITOM (2));
        MBdeftab (m);   /* [S [[I S S] r1]] */
MMechostr (MSKDEBUG, "tree_xml2_read_node : element name = %s\n", MMstartstr (m, MTOP (MMget (m, 0))));

        /*MMechostr (MSKDEBUG, "tree_xml2_read_node : cpt = %d\n", cpt);
        cpt = tree_xml2_read_node (m, node->children, cpt);*/

    }

    return cpt;

    /*MMpush (m, NIL);
    for (i = 0; i < cpt; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    OBJcallreflex (m, 1);
    return;*/
}

static void tree_xml2_read (mmachine m, int mobj)
{
    int cb, n, i;
    s_xml s;
    xmlNodePtr root;

    MMechostr (MSKDEBUG, "tree_xml2_read : entering\n");

    s = (s_xml) MMfetch (m, MTOP (mobj), S_LIBTREE_HANDLE);
    if (s == NULL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_read error : internal Scol structure is null\n");
        MMpush (m, NIL);
        return;
    }

    cb = OBJbeginreflex (m, ObjXmlTree, (int) s, S_LIBTREE_CB_READ);
    if (cb)
    {
        MMechostr (0, "tree_xml2_read : no callback 'READ' defined, error %d\n", cb);
        tree_xml2_read_node (m, NULL, 0);
        return;
    }

    root = xmlDocGetRootElement (s->doc);
    n = tree_xml2_read_node (m, root, 0);
/*MMechostr (MSKDEBUG, "tree_xml2_read : n = %d\n", n);*/
    MMpush (m, NIL);
    for (i = 0; i < n; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    OBJcallreflex (m, 1);

    return;
}

static char * tree_xml2_save_string (s_xml s, const char *enc, int format)
{
    char *res;
    xmlChar *str;
    int len;

    xmlDocDumpFormatMemoryEnc (s->doc, &str, &len, enc, format);
    str[len-1] = '\0';
    res = (char*) str;
    xmlFree (str);
    return res;
}

static char * tree_xml2_save_file (s_xml s, const char *filename, const char *enc, int format)
{
    /*char res[12];*/
    char *res = NULL;

    res = (char *) malloc (sizeof (char) * 12);
    snprintf (res, 10, "%d", xmlSaveFormatFileEnc (filename, s->doc, enc, format));
    return  res;
}

static void tree_xml2_save (mmachine m)
{
    int mobj, mfilename, menc, mformat;
    int format = 0;
    const char *enc = NULL;
    s_xml s;

    MMechostr (MSKDEBUG, "tree_xml2_save : entering\n");

    mformat = MMpull (m);
    menc = MMpull (m);
    mfilename = MMpull (m);
    mobj = MMpull (m);

    if (mformat != NIL)
        format = 1;
    if (menc != NIL)
        enc = MMstartstr (m, MTOP (menc));

    s = (s_xml) MMfetch (m, MTOP (mobj), S_LIBTREE_HANDLE);
    if (s == NULL)
    {
        MMechostr (MSKDEBUG, "tree_xml2_save error : internal Scol structure is null\n");
        MMpush (m, NIL);
        return;
    }

    if (mfilename != NIL)
    {
        char *res = NULL;

        res = tree_xml2_save_file (s, MMstartstr (m, MTOP (mfilename)), enc, format);
        Mpushstrbloc (m, res);
        free (res);
    }
        /*MMpush (m, MTOI (tree_xml2_save_file (s, MMstartstr (m, MTOP (mfilename)), enc, format)));*/
    else
        Mpushstrbloc (m, tree_xml2_save_string (s, enc, format));
    /*{
        tree_xml2_save_string (s, enc, format);
        MMpush (m, ITOM (0));
    }*/
    return;
}




/* PUBLIC */
int TREE_xml2CreateFromFile (mmachine m)
{
    tree_xml2_create_from_vm (m, 0);
    return 0;
}

int TREE_xml2CreateFromUrl (mmachine m)
{
    tree_xml2_create_from_vm (m, 1);
    return 0;
}

int TREE_xml2CreateFromString (mmachine m)
{
    tree_xml2_create_from_vm (m, 2);
    return 0;
}

int TREE_xml2CreateFromFILE (mmachine m)
{
    tree_xml2_create_from_vm (m, 3);
    return 0;
}

int TREE_xml2Read (mmachine m)
{
    int mobj;

    MMechostr (MSKDEBUG, "TREE_xml2Read : entering\n");

    mobj = MMget (m, 2);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "TREE_xml2Read error : object is nil !\n");
        MMpush (m, NIL);
        return 0;
    }

    OBJaddreflex (m, ObjXmlTree, S_LIBTREE_CB_READ);
    tree_xml2_read (m, mobj);
    return 0;
}

int TREE_xml2Save (mmachine m)
{
    int mobj;

    MMechostr (MSKDEBUG, "TREE_xml2Save : entering\n");

    mobj = MMget (m, 3);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "TREE_xml2Save error : object is nil !\n");
        MMpush (m, NIL);
        return 0;
    }
    tree_xml2_save (m);
    return 0;
}





/* API definition */
char* libxml2tree_name[TREE_LIBXML2_PKG_NB] =
{
    "ObjXmlTree2",

    "TREEXML2_ELEMENT", "TREEXML2_ATTRIBUTE",
    "TREEXML2_TEXT", "TREEXML2_COMMENT",
    "TREEXML2_CDATA",

    "_treexml2CreateFromFile",
    "_treexml2CreateFromUrl",
    "_treexml2CreateFromString",
    "_treexml2CreateFromFileObject",

    "_treexml2Read",
    "_treexml2Save"
};

int (*libxml2tree_fun[TREE_LIBXML2_PKG_NB]) (mmachine m) =
{
    NULL,

    SCTYPVAR (TREEXML2_ELEMENT), SCTYPVAR (TREEXML2_ATTRIBUTE),
    SCTYPVAR (TREEXML2_TEXT), SCTYPVAR (TREEXML2_COMMENT),
    SCTYPVAR (TREEXML2_CDATA),

    TREE_xml2CreateFromFile,
    TREE_xml2CreateFromUrl,
    TREE_xml2CreateFromString,
    TREE_xml2CreateFromFILE,

    TREE_xml2Read,
    TREE_xml2Save
};

int libxml2tree_narg[TREE_LIBXML2_PKG_NB] =
{
    TYPTYPE,

    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR,

    4,
    4,
    4,
    4,

    3,
    4
};

char* libxml2tree_type[TREE_LIBXML2_PKG_NB] =
{
    NULL,

    "I", "I",
    "I", "I",
    "I",

    "fun [Chn P S I] ObjXmlTree2",
    "fun [Chn S S I] ObjXmlTree2",
    "fun [Chn S S I] ObjXmlTree2",
    "fun [Chn File S I] ObjXmlTree2",

    "fun [ObjXmlTree2 fun [ObjXmlTree2 u0 [[S [[I S S] r1]] r1]] u1 u0] ObjXmlTree2",
    "fun [ObjXmlTree2 W S I] S"
};


int TREE_loadPackage (mmachine m)
{
    int k = 0;

    ObjXmlTree = OBJregister (TREE_LIBXML2_RFL_NB, 1, ObjXmlTreeDestroy, "ObjXmlTreeType");
    k = PKhardpak (m, "LIBXML2_TREEengine", TREE_LIBXML2_PKG_NB, libxml2tree_name, libxml2tree_fun, libxml2tree_narg, libxml2tree_type);
    return k;
}

