/*
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_reader.h"


/* PRIVATE */
static int ObjXmlReaderDestroy (mmachine m, int handsys, int mobj)
{
    s_xml s;
    int res;

    MMechostr(MSKDEBUG, "ObjXmlReaderDestroy : entering ...\n");

	/* return the object handle */
    s = (s_xml) MMfetch (m, MTOP (mobj), S_LIBSAX2_HANDLE);
    if (s == NULL)
    {
        MMechostr(MSKDEBUG, "ObjXmlReaderDestroy : 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, "ObjXmlReaderDestroy : thread already canceled\n");
    #elif ((defined _WIN32) || (defined __WIN32__))
    res = TerminateThread (s->sub->thread, 0);
    if (res == 0)
        MMechostr(MSKDEBUG, "ObjXmlReaderDestroy : thread already canceled\n");
    #endif
    free (s->sub);
    free (s);
	/* unregister this object to the Scol machine */
    MMstore (m, MTOP (mobj), S_LIBSAX2_HANDLE, (int) NULL);
    MMechostr(MSKDEBUG, "ObjXmlReaderDestroy : object has been destroyed\n");
    return 0;
}

static int reader_xml2_destroy (mmachine m, int mobj)
{
    int res;
    s_xml s;

    MMechostr(MSKDEBUG, "reader_xml2_destroy : entering ...\n");
    s = (s_xml) MMfetch (m, MTOP (mobj), S_LIBREADER_HANDLE);
    if (s == NULL)
        return 1;

    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, "reader_xml2_destroy : thread already canceled\n");
    #elif ((defined _WIN32) || (defined __WIN32__))
    res = TerminateThread (s->sub->thread, 0);
    if (res == 0)
        MMechostr(MSKDEBUG, "reader_xml2_destroy : thread already canceled\n");
    #endif
    OBJdelTM (m, ObjXmlReader, mobj);
    free (s->sub);
    free (s);
    return 0;
}

#if ((defined linux) || (defined __linux))
static void *reader_xml2_new (void *user_data)
#elif ((defined _WIN32) || (defined __WIN32__))
static DWORD WINAPI reader_xml2_new (LPVOID user_data)
#endif
{
    s_xml s;
    int cb;

    MMechostr (MSKDEBUG, "reader_xml2_new : entering ...\n");

    s = (s_xml) user_data;

    MMechostr (MSKDEBUG, "reader_xml2_new : ==%s== ==%s== ...\n", s->sub->buffer, s->sub->filename);
    if (s->sub->buffer == NULL)
        s->reader = xmlReaderForFile (s->sub->filename, s->sub->enc_to, s->sub->flags);
    else
        s->reader = xmlReaderForMemory (s->sub->buffer, strlen (s->sub->buffer), NULL, s->sub->enc_to, s->sub->flags);
    cb = OBJbeginreflex (s->sub->sm, ObjXmlReader, (int) s, S_LIBREADER_CB_ENDED);
    if (cb)
    {
        MMechostr (MSKDEBUG, "reader_xml2_new : no cb %d ...\n", cb);
        MMpush (s->sub->sm, NIL);
        return 1;
    }
    if (NULL == s->reader)
    {
        MMechostr(MSKDEBUG, "reader_xml2_new error : unable to create the reader ...\n");
        MMpush (s->sub->sm, ITOM (1));
        OBJcallreflex (s->sub->sm, 1);
        /*pthread_exit ((void *) mobj);*/
        #if ((defined linux) || (defined __linux))
        pthread_exit ((void *) 1);
        return 1;
        #elif ((defined _WIN32) || (defined __WIN32__))
        ExitThread (1);
        #endif
    }

    MMpush (s->sub->sm, ITOM (0));
    OBJcallreflex (s->sub->sm, 1);
    /*pthread_exit ((void *) mobj);*/
    #if ((defined linux) || (defined __linux))
    pthread_exit ((void *) 1);
    return 0;
    #elif ((defined _WIN32) || (defined __WIN32__))
    xmlFreeTextReader (s->reader);
    ExitThread (1);
    #endif
}

int reader_xml2_create (mmachine m, int typ)
{
    int mchannel, msrc, menc, mflags, mcb, mup;
    #if ((defined linux) || (defined __linux))
    int res;
    #elif ((defined _WIN32) || (defined __WIN32__))
    /*DWORD dwThreadId;*/
    #endif

    /*void *resthread;*/
    s_xml s;

    MMechostr (MSKDEBUG, "reader_xml2_create : entering ...\n");

    mup = MMpull (m);
    mcb = MMpull (m);
    mflags = MMpull (m);
    menc = MMpull (m);
    msrc = MMpull (m);
    mchannel = MMget (m, 0);

    if (mchannel == NIL)
    {
        MMechostr (MSKDEBUG, "reader_xml2_create error : channel is nil ...\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    if (msrc == NIL)
    {
        MMechostr (MSKDEBUG, "reader_xml2_create error : source is nil ...\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }

    s = malloc (sizeof (struct S_xml));
    if(s == NULL)
    {
        MMechostr (MSKDEBUG, "reader_xml2_create error : not enough memory ...\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    s->sub = malloc (sizeof (struct Subxml));
    if (typ)
    {
        s->sub->buffer = MMstartstr (m, MTOP (msrc));
        s->sub->filename = NULL;
    }
    else
    {
        s->sub->filename = MMstartstr (m, MTOP (msrc));
        s->sub->buffer = NULL;
    }
    if (menc != NIL)
        s->sub->enc_to = MMstartstr (m, MTOP (menc));
    else
        s->sub->enc_to = NULL;
    if (mflags != NIL)
        s->sub->flags = MTOI (mflags);
    else
        s->sub->flags = 0;

    s_xml2create_object_reader (m, s);

    MMpush (m, mcb);    /* reflex */
    MMpush (m, mup);    /* user parameter */
    OBJaddreflex (m, ObjXmlReader, S_LIBREADER_CB_ENDED);
    s->sub->sm = m;
    #if ((defined linux) || (defined __linux))
    s->sub->mthread = pthread_self ();
    res = pthread_create ((&(s->sub->thread)), NULL, reader_xml2_new, (void *) s);
    #elif ((defined _WIN32) || (defined __WIN32__))
    s->sub->mthread = GetCurrentThreadId ();
    s->sub->thread = CreateThread (/*(LPSECURITY_ATTRIBUTES) THREAD_TERMINATE*/NULL, 0, reader_xml2_new, s, 0, NULL);
    if (s->sub->thread == NULL)
        winHandleThreadError (TEXT ("reader_xml2_create"));
    #endif

    /*pthread_join (s->sub->thread, &resthread);
    MMpush (m, (int) resthread);*/
    return 0;
}

static void reader_xml2_read (mmachine m, int obj, int flags)
{
    s_xml s;
    int res, i = 0, cpt = 0, isdone = 0;

    MMechostr (MSKDEBUG, "reader_xml2_read : entering ...\n");

    s = (s_xml) MMfetch (m, obj, S_LIBREADER_HANDLE);

    res = xmlTextReaderRead (s->reader);
    while (res == 1)
    {
        if ((flags & READERXML2_VERSION) && (!(isdone & READERXML2_VERSION)))
        {
            MMpush (m, ITOM (READERXML2_VERSION));
            Mpushstrbloc (m, (char *) xmlTextReaderConstXmlVersion (s->reader));
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
            isdone = isdone|READERXML2_VERSION;
        }
        if ((flags & READERXML2_ENCODING) && (!(isdone & READERXML2_ENCODING)))
        {
            MMpush (m, ITOM (READERXML2_ENCODING));
            Mpushstrbloc (m, (char *) xmlTextReaderConstEncoding (s->reader));
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
            isdone = isdone|READERXML2_ENCODING;
        }
        if ((flags & READERXML2_ISSTANDALONE) && (!(isdone & READERXML2_ISSTANDALONE)))
        {
            char c[3];

            MMpush (m, ITOM (READERXML2_ISSTANDALONE));
            snprintf (c, 2, "%d", xmlTextReaderStandalone (s->reader));
            Mpushstrbloc (m, (char *) c);
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
            isdone = isdone|READERXML2_ISSTANDALONE;
        }
        if ((flags & READERXML2_LANGUAGE) && (!(isdone & READERXML2_LANGUAGE)))
        {
            xmlChar *c;

            c = (xmlChar*) xmlTextReaderXmlLang (s->reader);
            MMpush (m, ITOM (READERXML2_LANGUAGE));
            Mpushstrbloc (m, (char*) c);
            xmlFree (c);
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
            isdone = isdone|READERXML2_LANGUAGE;
        }
        if (flags & READERXML2_NODEVALUE)
        {
            if (xmlTextReaderNodeType (s->reader) == XML_READER_TYPE_TEXT)
            {
                MMpush (m, ITOM (READERXML2_NODEVALUE));
                Mpushstrbloc (m, (char*) xmlTextReaderConstValue (s->reader));
                MMpush (m, ITOM (2));
                MBdeftab (m);
                cpt++;
            }
        }
        if (flags & READERXML2_NODENAME)
        {
            if (xmlTextReaderNodeType (s->reader) == XML_READER_TYPE_ELEMENT)
            {
                MMpush (m, ITOM (READERXML2_NODENAME));
                Mpushstrbloc (m, (char*) xmlTextReaderConstName (s->reader));
                MMpush (m, ITOM (2));
                MBdeftab (m);
                cpt++;
            }
        }
        if (flags & READERXML2_VALIDITY)
        {
            char c[3];

            snprintf (c, 2, "%d", xmlTextReaderIsValid (s->reader));
            MMpush (m, ITOM (READERXML2_VALIDITY));
            Mpushstrbloc (m, (char*) c);
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;

        }
        if (flags & READERXML2_DEPTH)
        {
            char c[5];

            snprintf (c, 4, "%d", xmlTextReaderDepth (s->reader));
            MMpush (m, ITOM (READERXML2_DEPTH));
            Mpushstrbloc (m, (char*) c);
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
        }
        if (flags & READERXML2_NUMBERLINECOL)
        {
            char c[16];

            snprintf (c, 15, "%d;%d", xmlTextReaderGetParserLineNumber (s->reader),
                      xmlTextReaderGetParserColumnNumber (s->reader));
            MMpush (m, ITOM (READERXML2_NUMBERLINECOL));
            Mpushstrbloc (m, (char*) c);
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
        }
        if (flags & READERXML2_NODETYPE)
        {
            int t = 0;

            t = xmlTextReaderNodeType (s->reader);
            MMpush (m, ITOM (READERXML2_NODETYPE));
            switch (t)
            {
                case (XML_READER_TYPE_NONE):
                    Mpushstrbloc (m, "NONE");
                    break;
                case (XML_READER_TYPE_ELEMENT):
                    Mpushstrbloc (m, "ELEMENT_START");
                    break;
                case (XML_READER_TYPE_ATTRIBUTE):
                    Mpushstrbloc (m, "ATTRIBUTE");
                    break;
                case (XML_READER_TYPE_TEXT):
                    Mpushstrbloc (m, "TEXT");
                    break;
                case (XML_READER_TYPE_CDATA):
                    Mpushstrbloc (m, "CDATA");
                    break;
                case (XML_READER_TYPE_COMMENT):
                    Mpushstrbloc (m, "COMMENT");
                    break;
                case (XML_READER_TYPE_DOCUMENT):
                    Mpushstrbloc (m, "DOCUMENT");
                    break;
                case (XML_READER_TYPE_DOCUMENT_TYPE):
                    Mpushstrbloc (m, "DOCUMENT_TYPE");
                    break;
                case (XML_READER_TYPE_DOCUMENT_FRAGMENT):
                    Mpushstrbloc (m, "DOCUMENT_FRAGMENT");
                    break;
                case (XML_READER_TYPE_END_ELEMENT):
                    Mpushstrbloc (m, "ELEMENT_END");
                    break;
                case (XML_READER_TYPE_XML_DECLARATION):
                    Mpushstrbloc (m, "XML_DECLARATION");
                    break;
                case (XML_READER_TYPE_ENTITY):
                    Mpushstrbloc (m, "ENTITY_START");
                    break;
                case (XML_READER_TYPE_END_ENTITY):
                    Mpushstrbloc (m, "ENTITY_END");
                    break;
                default:
                    MMpush (m, NIL);
            }
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
        }
        if (flags & READERXML2_NODESTRING)
        {
            MMpush (m, ITOM (READERXML2_NODESTRING));
            Mpushstrbloc (m, (char*) xmlTextReaderReadString (s->reader));
            MMpush (m, ITOM (2));
            MBdeftab (m);
            cpt++;
        }
        if (((flags & READERXML2_ATTRIBUTES) || (flags & READERXML2_ATTRIBUTESVALUES))
            && (xmlTextReaderNodeType (s->reader) == XML_READER_TYPE_ELEMENT))
        {
            while (xmlTextReaderMoveToNextAttribute (s->reader))
            {
                xmlChar *attrvalue, *attrname;

                attrname = xmlTextReaderName (s->reader);
                attrvalue = xmlTextReaderValue (s->reader);

                if ((flags & READERXML2_ATTRIBUTES) && (flags & READERXML2_ATTRIBUTESVALUES))
                {
                    MMpush (m, ITOM (READERXML2_ATTRIBUTES));
                    Mpushstrbloc (m, (char *) attrname);
                    MMpush (m, ITOM (2));
                    MBdeftab (m);
                    cpt++;
                    MMpush (m, ITOM (READERXML2_ATTRIBUTESVALUES));
                    Mpushstrbloc (m, (char *) attrvalue);
                    MMpush (m, ITOM (2));
                    MBdeftab (m);
                    cpt++;
                }

                else if (flags & READERXML2_ATTRIBUTES)
                {
                    MMpush (m, ITOM (READERXML2_ATTRIBUTES));
                    Mpushstrbloc (m, (char *) attrname);
                    MMpush (m, ITOM (2));
                    MBdeftab (m);
                    cpt++;
                }

                else if (flags & READERXML2_ATTRIBUTESVALUES)
                {
                    MMpush (m, ITOM (READERXML2_ATTRIBUTESVALUES));
                    Mpushstrbloc (m, (char *) attrvalue);
                    MMpush (m, ITOM (2));
                    MBdeftab (m);
                    cpt++;
                }

                xmlFree (attrname);
                xmlFree (attrvalue);
            }
        }
        res = xmlTextReaderRead (s->reader);
    }
    /* create a Scol list */
    MMpush (m, NIL);
    for (i = 0; i < cpt; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    return;
}

static void reader_xml2_set_entity (mmachine m, int obj, int value)
{
    s_xml s;

    MMechostr (MSKDEBUG, "reader_xml2_set_entity : entering ...\n");

    s = (s_xml) MMfetch (m, obj, S_LIBREADER_HANDLE);
    if (0 == xmlTextReaderSetParserProp (s->reader, XML_PARSER_SUBST_ENTITIES, value))
        MMpush (m, 0);
    else
        MMpush (m, NIL);
    return;
}

static void reader_xml2_get_entity (mmachine m, int obj)
{
    s_xml s;

    MMechostr (MSKDEBUG, "reader_xml2_get_entity : entering ...\n");

    s = (s_xml) MMfetch (m, obj, S_LIBREADER_HANDLE);
    MMpush (m, xmlTextReaderGetParserProp (s->reader, XML_PARSER_SUBST_ENTITIES));
    return;
}







/* PUBLIC */
int READER_xml2CreateFromFile (mmachine m)
{
    return reader_xml2_create (m, 0);
}

int READER_xml2CreateFromUrl (mmachine m)
{
    return reader_xml2_create (m, 0);
}

int READER_xml2CreateFromString (mmachine m)
{
    return reader_xml2_create (m, 1);
}

int READER_xml2Destroy (mmachine m)
{
    int mobj, res = 0;

    MMechostr (MSKDEBUG, "READER_xml2Destroy : entering ...\n");

    mobj = MMpull (m);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "READER_xml2Destroy error : object is nil ...\n");
        MMpush (m, NIL);
        return 0;
    }

    res = reader_xml2_destroy (m, mobj);
    if (!res)
    {
        MMpush (m, ITOM (0));
        return 0;
    }
    MMpush (m, NIL);
    return 0;
}

int READER_xml2Read (mmachine m)
{
    int mobj, mflags;
    int flags = 0;

    MMechostr (MSKDEBUG, "READER_xml2Read : entering ...\n");

    mflags = MMpull (m);
    mobj = MMpull (m);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "READER_xml2Read error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }
    if (mflags != NIL)
        flags = MTOI (mflags);
    reader_xml2_read (m, MTOP (mobj), flags);
    return 0;
}

int READER_xml2EntitySet (mmachine m)
{
    int mobj, mvalue;
    int value = 0;

    MMechostr (MSKDEBUG, "READER_xml2EntitySet : entering ...\n");

    mvalue = MTOI (MMpull (m));
    mobj = MMpull (m);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "READER_xml2EntitySet error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }
    if ((mvalue >= 0) || (mvalue <= 1))
        value = mvalue;
    reader_xml2_set_entity (m, MTOP (mobj), value);
    return 0;
}

int READER_xml2EntityGet (mmachine m)
{
    int mobj;

    MMechostr (MSKDEBUG, "READER_xml2EntityGet : entering ...\n");

    mobj = MMpull (m);
    if (mobj == NIL)
    {
        MMechostr (MSKDEBUG, "READER_xml2EntityGet error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }
    reader_xml2_get_entity (m, MTOP (mobj));
    return 0;
}







/* API definition */
char* libxml2read_name[READER_LIBXML2_PKG_NB] =
{
    "ObjXmlRead2",

    "READXML2_VERSION", "READXML2_ENCODING",
    "READXML2_STANDALONE", "READXML2_LANG",
    "READXML2_NODE_VALUE", "READXML2_NODE_NAME",
    "READXML2_VALIDITY", "READXML2_ATTRIBUTES",
    "READXML2_ATTRVALUES", "READXML2_NODE_DEPTH",
    "READXML2_LINECOL", "READXML2_NODE_TYPE",
    "READXML2_NODE_STRING",

    "READXML2_OPTION_RECOVER", "READXML2_OPTION_NONET",
    "READXML2_OPTION_NOBLANKS", "READXML2_NSCLEAN",
    "READXML2_OPTION_NONE",

    "_readxml2CreateFromFile",
    "_readxml2CreateFromUrl",
    "_readxml2CreateFromString",
    "_readxml2Destroy",
    "_readxml2Read",
    "_readxml2EntitySet",
    "_readxml2EntityGet"
};

int (*libxml2read_fun[READER_LIBXML2_PKG_NB]) (mmachine m) =
{
    NULL,

    SCTYPVAR (READERXML2_VERSION), SCTYPVAR (READERXML2_ENCODING),
    SCTYPVAR (READERXML2_ISSTANDALONE), SCTYPVAR (READERXML2_LANGUAGE),
    SCTYPVAR (READERXML2_NODEVALUE), SCTYPVAR (READERXML2_NODENAME),
    SCTYPVAR (READERXML2_VALIDITY), SCTYPVAR (READERXML2_ATTRIBUTES),
    SCTYPVAR (READERXML2_ATTRIBUTESVALUES), SCTYPVAR (READERXML2_DEPTH),
    SCTYPVAR (READERXML2_NUMBERLINECOL), SCTYPVAR (READERXML2_NODETYPE),
    SCTYPVAR (READERXML2_NODESTRING),

    SCTYPVAR (READERXML2_OPTION_RECOVER), SCTYPVAR (READERXML2_OPTION_NONET),
    SCTYPVAR (READERXML2_OPTION_NOBLANKS), SCTYPVAR (READERXML2_NSCLEAN),
    SCTYPVAR (READERXML2_OPTION_NONE),

    READER_xml2CreateFromFile,
    READER_xml2CreateFromUrl,
    READER_xml2CreateFromString,
    READER_xml2Destroy,
    READER_xml2Read,
    READER_xml2EntitySet,
    READER_xml2EntityGet
};

int libxml2read_narg[READER_LIBXML2_PKG_NB] =
{
    TYPTYPE,

    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR,

    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR,

    6,   /* _readxml2CreateFromFile */
    6,   /* _readxml2CreateFromUrl */
    6,  /* _readxml2CreateFromString */
    1,   /* _readxml2Destroy */
    2,   /* _readxml2Read */
    2,   /* _readxml2EntitySet */
    1   /* _readxml2EntityGet */
};

char* libxml2read_type[READER_LIBXML2_PKG_NB] =
{
    NULL,

    "I", "I",
    "I", "I",
    "I", "I",
    "I", "I",
    "I", "I",
    "I", "I",
    "I",

    "I", "I",
    "I", "I",
    "I",

    "fun [Chn P S I fun [ObjXmlRead2 u0 I] u1 u0] ObjXmlRead2",                     /* _readxml2CreateFromFile */
    "fun [Chn S S I fun [ObjXmlRead2 u0 I] u1 u0] ObjXmlRead2",                     /* _readxml2CreateFromUrl */
    "fun [Chn S S I fun [ObjXmlRead2 u0 I] u1 u0] ObjXmlRead2",                     /* _readxml2CreateFromString */
    "fun [ObjXmlRead2] I",                                                          /* _readxml2Destroy */
    "fun [ObjXmlRead2 I] [[I S] r1]",                                                /* _readxml2Read */
    "fun [ObjXmlRead2 I] I",                                                         /* _readxml2EntitySet */
    "fun [ObjXmlRead2] I"                                                         /* _readxml2EntityGet */
};

int READER_loadPackage (mmachine m)
{
    int k = 0;

    ObjXmlReader = OBJregister (READER_LIBXML2_RFL_NB, 1, ObjXmlReaderDestroy, "ObjXmlReaderType");
    k = PKhardpak (m, "LIBXML2_READERengine", READER_LIBXML2_PKG_NB, libxml2read_name, libxml2read_fun, libxml2read_narg, libxml2read_type);
    return k;
}
