/*!
 * @file libharkio3_xml.c
 *
 * @author Takeshi Mizumoto
 * @version 0.1
 * @brief  harkio_XML の API 群。 XML の読み込み・書き込みなどを受け持つ。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "libharkio3.h"
#include <libxml/xmlreader.h>

int harkio_XML_internal_processNode(xmlTextReaderPtr reader, harkio_XML *xml);
int harkio_XML_internal_strcount(const char *str, char key);
int harkio_XML_internal_write(harkio_XML *xml, FILE *fstream, char **cstream);
void pushStream(char **buffer, char *tmp);

enum harkio_XML_status {
    STATE_NONE,
    STATE_CONFIG,
    STATE_POSITIONS,
    STATE_NEIGHBORS,
    STATE_CFG_COMMENT,
    STATE_CHANNELS,
    STATE_CFG_SA,
    STATE_CFG_TSPpath,
    STATE_CFG_TSPOffset,
    STATE_CFG_PeakSearch,
    STATE_CFG_nfft,
    STATE_CFG_samplingRate,
    STATE_CFG_signalMax,
    STATE_CFG_TSPLength
};

/*!
 * @brief コンストラクタ。新たな harkio_XML オブジェクトを生成する。
 * @return harkio_XML へのポインタ。失敗すると NULL ポインタを返す。
 * @details harkio_XML_delete でメモリ解放を行う必要がある。
 */
harkio_XML* harkio_XML_new(void) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    harkio_XML *xml = (harkio_XML *) malloc(sizeof(harkio_XML));

    xml->poses = NULL;
    xml->nbrs = NULL;
    xml->cfg = NULL;
    return xml;
}

/*!
 * @brief コンストラクタ。ファイルを読み込んで、新たな harkio_XML オブジェクトを生成する。
 * @param[in] xmlpath  読み込む XML ファイルへのパス
 * @param[in] dtdpath  [未実装] 読み込む XML ファイルを検査するための dtd ファイルへのパス。
 * @return harkio_XML へのポインタ。失敗すると NULL ポインタを返す。
 * @details harkio_XML_delete でメモリ解放を行う必要がある。
 */
harkio_XML* harkio_XML_newFromXML(char *xmlpath, char *dtdpath) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    int ret;
    xmlTextReaderPtr reader;
    harkio_XML *xml = (harkio_XML *) malloc(sizeof(harkio_XML));

    xml->poses = NULL;
    xml->nbrs = NULL;
    xml->cfg = NULL;

    reader = xmlNewTextReaderFilename(xmlpath);
    if (!reader) {
        harkio_Log_printf(harkio_Log_Error, "xmlNewTextReaderFilename error");
        xmlFreeTextReader(reader);
        free(xml);
        return NULL;
    }

    ret = xmlTextReaderRead(reader);
    while (ret == 1) {
        harkio_XML_internal_processNode(reader, xml);
        ret = xmlTextReaderRead(reader);
    }

    xmlFreeTextReader(reader);
    if (ret == -1) {
        harkio_Log_printf(harkio_Log_Error, "xmlTextReaderRead error");
        return NULL;
    }

    return xml;
}

/*!
 * @brief コンストラクタ。harkio_XML のメンバへのポインタを受け取って、新たな harkio_XML オブジェクトを生成する。
 * @param[in] ps    XML で表現する位置群を表す。
 * @param[in] nbrs  ps の近接関係を表す
 * @param[in] cfg   XML の一般設定を表す。
 * @return harkio_XML へのポインタ。失敗すると NULL ポインタを返す。
 * @details harkio_XML_delete でメモリ解放を行う必要がある。メンバは NULL でもよい。
 */
harkio_XML* harkio_XML_newFromData(harkio_Positions *ps, harkio_Neighbors *nbrs, harkio_Config *cfg) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    harkio_XML *xml = (harkio_XML *) malloc(sizeof(harkio_XML));
    xml->poses = ps;
    xml->nbrs = nbrs;
    xml->cfg = cfg;

    return xml;
}

/*!
 * @brief コンストラクタ。XMLが格納された文字列へのポインタから、harkio_XML オブジェクトを生成する
 * @param[in] buffer   XML が格納された文字列へのポインタ。
 * @param[in] len      buffer の長さ。
 * @param[in] dtdpath  [未実装] 読み込む XML ファイルを検査するための dtd ファイルへのパス。
 * @return harkio_XML へのポインタ。失敗すると NULL ポインタを返す。
 * @details harkio_XML_delete でメモリ解放を行う必要がある。メンバは NULL でもよい。
 */
harkio_XML* harkio_XML_newFromXMLbuffer(char *buffer, int len, char *dtdpath) {
    // fprintf(stderr, "newFromXMLbuffer, %s\n", buffer);
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);
    harkio_Log_printf(harkio_Log_Trace, buffer);

    int ret;
    xmlTextReaderPtr reader;
    harkio_XML *xml = (harkio_XML *) malloc(sizeof(harkio_XML));

    xml->poses = NULL;
    xml->nbrs = NULL;
    xml->cfg = NULL;

    reader = xmlReaderForMemory(buffer, len, "", NULL, 0);
    if (!reader) {
        harkio_Log_printf(harkio_Log_Error, "xmlReaderForMemory error");
        xmlFreeTextReader(reader);
        free(xml);
        return NULL;
    }

    ret = xmlTextReaderRead(reader);

    while (ret == 1) {

        harkio_XML_internal_processNode(reader, xml);
        ret = xmlTextReaderRead(reader);
    }

    xmlFreeTextReader(reader);
    if (ret == -1) {
        harkio_Log_printf(harkio_Log_Error, "xmlTextReaderRead error");
        free(xml);
        return NULL;
    }
    return xml;
}

/*!
 * @brief デストラクタ。harkio_XML のメモリを解放する。
 * @param[in] xml   メモリを解放するオブジェクトへのポインタへのポインタ
 * @details 解放に成功すると xml には NULL が代入される。メンバのいくつかが NULL でもよい。
 */
void harkio_XML_delete(harkio_XML **xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    if (xml == NULL || *xml == NULL) {
        return;
    }

    // fprintf(stderr, "poses: %p\n", (*xml)->poses);
    if ((*xml)->poses) {
        harkio_Positions_delete(&((*xml)->poses));
        // fprintf(stderr, "---------------------------------%p", ((*xml)->poses));
    }
    // fprintf(stderr, "nbrs: %p\n", (*xml)->nbrs);
    if ((*xml)->nbrs) {
        harkio_Neighbors_delete(&((*xml)->nbrs));
    }
    // fprintf(stderr, "cfg  : %p\n", (*xml)->cfg);
    if ((*xml)->cfg) {
        harkio_Config_delete(&((*xml)->cfg));
    }

    free((*xml));
    *xml = NULL;
}
/*----------------------------------------------------------------------------*/
/* Operation                                                                  */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------*/
/*                                                          */
/*----------------------------------------------------------*/
harkio_Neighbors *harkio_XML_getNeighbors(harkio_XML *xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    return xml->nbrs;
}

/*----------------------------------------------------------*/
/*                                                          */
/*----------------------------------------------------------*/
harkio_Positions *harkio_XML_getPositions(harkio_XML *xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    return xml->poses;
}

/*----------------------------------------------------------*/
/*                                                          */
/*----------------------------------------------------------*/
harkio_Config* harkio_XML_getConfig(harkio_XML *xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    return xml->cfg;
}

/*----------------------------------------------------------------------------*/
/* Utility                                                                    */
/*----------------------------------------------------------------------------*/

/*!
 * @brief harkio_XML をファイルに保存する。
 * @param[in] xml       保存される harkio_XML オブジェクト
 * @param[in] fstream   保存したいファイルへのハンドル
 * @return 成功すると EXIT_SUCCESS, 失敗すると EXIT_FAILURE を返す。
 */
int harkio_XML_write(harkio_XML *xml, FILE *fstream) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    if (fstream == NULL) {
        harkio_Log_printf(harkio_Log_Error, "File pointer is NULL");
        return EXIT_FAILURE;
    }
    return harkio_XML_internal_write(xml, fstream, NULL);
}

int harkio_XML_internal_writePositions(harkio_Positions*, FILE*, char**);

/*!
 * @brief harkio_Positions をXMLファイルに保存する。
 * @param[in] poses       保存される harkio_XML オブジェクト
 * @param[in] fstream   保存したいファイルへのハンドル
 * @details 逐次的に保存したい場合に使用
 * @see harkio_XML_writeHead, harkio_XML_writeTail
 */
int harkio_XML_writePositions(harkio_Positions *poses, FILE *outstream) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    if (outstream == NULL) {
        harkio_Log_printf(harkio_Log_Error, "File pointer is NULL");
        return EXIT_FAILURE;
    }

    return harkio_XML_internal_writePositions(poses, outstream, NULL);
}
/*!
 * @brief harkio_Positions をXMLファイルに保存する。
 * @param[in] fstream   保存したいファイルへのハンドル
 * @details 逐次的に保存したい場合に使用。
 *         harkio_XML_writeHead, harkio_XML_writePositions, harkio_XML_writeTail
 *         の順番で実行すれば、harkio_XML_writePosition で書くたびにその harkio_Positions のメモリを解放できる。
 * @see harkio_XML_writePositions, harkio_XML_writeTail
 */
int harkio_XML_writeHead(FILE *outstream) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);
    assert(outstream);

    fprintf(outstream, "<hark_xml version=\"" HARKIO_VER "\">\n");

    return EXIT_SUCCESS;
}
/*!
 * @brief harkio_Positions をXMLファイルに保存する。
 * @param[in] fstream   保存したいファイルへのハンドル
 * @details 逐次的に保存したい場合に使用
 * @see harkio_XML_writeHead, harkio_XML_writePositions
 */
int harkio_XML_writeTail(FILE *outstream) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);
    assert(outstream);

    fprintf(outstream, "</hark_xml>\n");

    return EXIT_SUCCESS;
}

/*!
 * @brief harkio_XML をバッファに保存する。
 * @param[in] xml       保存される harkio_XML オブジェクト
 * @details harkio_XML の XML表現を、ファイルに書き出さずに文字列として渡す。
 * @return 成功すれば文字列へのポインタ、失敗すれば NULL
 */
char* harkio_XML_writeBuffer(harkio_XML *xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    int ret;
    char *cstream;

    ret = harkio_XML_internal_write(xml, NULL, &cstream);
    if (ret == EXIT_FAILURE)
        return NULL;

    return cstream;
}


/*!
 * @brief 内部処理用関数
 */
static int harkio_XML_internal_writePosition__(harkio_Position* p, char* buf, char** result) {
    assert(p);
    assert(buf);

    static const char* PLUS_ZERO = "0.0000";
    static const char* MINUS_ZERO = "-0.0000";

    if (p->sys == Cartesian) {
        char x[100], y[100], z[100];
        sprintf(x, "%.4f", p->coord[0]);
        if (memcmp(x, MINUS_ZERO, 7) == 0) {
            memcpy(x, PLUS_ZERO, 7);
        }
        sprintf(y, "%.4f", p->coord[1]);
        if (memcmp(y, MINUS_ZERO, 7) == 0) {
            memcpy(y, PLUS_ZERO, 7);
        }
        sprintf(z, "%.4f", p->coord[2]);
        if (memcmp(z, MINUS_ZERO, 7) == 0) {
            memcpy(z, PLUS_ZERO, 7);
        }
        sprintf(buf, "        <position x=\"%s\" y=\"%s\" z=\"%s\" id=\"%d\"", x, y, z, p->id);
        pushStream(result, buf);

        if (p->path) {
            sprintf(buf, " path=\"%s\"/>\n", p->path);
        } else {
            sprintf(buf, "/>\n");
        }
        pushStream(result, buf);
    } else if (p->sys == Polar) {
        sprintf(buf, "        <position azimuth=\"%.4f\" elevation=\"%.4f\" radius=\"%.4f\" id=\"%d\"",
                p->coord[0], p->coord[1], p->coord[2], p->id);
        pushStream(result, buf);

        if (p->path) {
            sprintf(buf, " path=\"%s\"/>\n", p->path);
        } else {
            sprintf(buf, "/>\n");
        }
        pushStream(result, buf);
    }

    return EXIT_SUCCESS;
}

/*!
 * @brief 内部処理用関数
 */
static int harkio_XML_internal_writePositions__(harkio_Positions* p, char* buf, char** result) {
    assert(p);
    assert(buf);

    int i = 0;
    sprintf(buf, "    <positions");
    pushStream(result, buf);

    if (p->type != NULL) {
        sprintf(buf, " type=\"%s\"", p->type);
        pushStream(result, buf);
    }

    if (p->frame >= 0) {
        sprintf(buf, " frame=\"%d\"", p->frame);
        pushStream(result, buf);
    }
    if (p->size > 0){
        switch (p->pos[0]->sys) {
        case Cartesian:
            sprintf(buf, " coordinate=\"cartesian\">\n");
            pushStream(result, buf);
            break;
        case Polar:
            sprintf(buf, " coordinate=\"polar\">\n");
            pushStream(result, buf);
            break;
        default:
            harkio_Log_printf(harkio_Log_Error, "XML positions type is wrong. type must be Cartesian or Polar");
            return EXIT_FAILURE;
        }
    }
    else{
        pushStream(result, ">\n");
    }

    for (i = 0; i < p->size; i++) {
        harkio_XML_internal_writePosition__(p->pos[i], buf, result);
    }

    sprintf(buf, "    </positions>\n");
    pushStream(result, buf);

    return EXIT_SUCCESS;
}

/*!
 * @brief 内部処理用関数
 */
int harkio_XML_internal_write(harkio_XML *xml, FILE *fstream, char **cstream) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    int i;
    char *result = NULL;
    char buf[65535];

    sprintf(buf, "<hark_xml version=\"%s\">\n", HARKIO_VER);
    pushStream(&result, buf);

    // print config
    if (xml->cfg) {
        sprintf(buf, "    <config>\n");
        pushStream(&result, buf);
        sprintf(buf, "        <comment>%s</comment>\n", harkio_Config_getComment(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <SynchronousAverage>%d</SynchronousAverage>\n",
                harkio_Config_getSynchronousAverage(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <TSPpath>%s</TSPpath>\n", harkio_Config_getTSPpath(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <TSPOffset>%d</TSPOffset>\n", harkio_Config_getTSPoffset(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <PeakSearch from=\"%d\" to=\"%d\"/>\n", harkio_Config_getPeakSearchFrom(xml->cfg),
                harkio_Config_getPeakSearchTo(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <nfft>%d</nfft>\n", harkio_Config_getNfft(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <samplingRate>%d</samplingRate>\n", harkio_Config_getSamplingRate(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <signalMax>%d</signalMax>\n", harkio_Config_getSignalMax(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "        <TSPLength>%d</TSPLength>\n", harkio_Config_getTSPLength(xml->cfg));
        pushStream(&result, buf);
        sprintf(buf, "    </config>\n");
        pushStream(&result, buf);
    }

    // print positions
    if (xml->poses) {
        harkio_XML_internal_writePositions__(xml->poses, buf, &result);
    }

    // print neighbors
    if (xml->nbrs) {
        sprintf(buf, "    <neighbors algorithm=");
        pushStream(&result, buf);
        switch (xml->nbrs->alg) {
        case NearestNeighbor:
            sprintf(buf, "\"NearestNeighbor\">\n");
            pushStream(&result, buf);
            break;
        default:
            harkio_Log_printf(harkio_Log_Error, "XML neighbors algorithm is wrong. Algorithm must be NearestNeighbor");
            return EXIT_FAILURE;
        }
        for (i = 0; i < xml->nbrs->size; i++) {
            int j;
            sprintf(buf, "        <neighbor id=\"%d\" ids=\"", xml->nbrs->ids[i]);
            pushStream(&result, buf);
            for (j = 0; j < xml->nbrs->numNeighbors[i]; j++) {
                sprintf(buf, "%d;", xml->nbrs->neighbors[i][j]);
                pushStream(&result, buf);
            }
            sprintf(buf, "\"/>\n");
            pushStream(&result, buf);
        }
        sprintf(buf, "    </neighbors>\n");
        pushStream(&result, buf);
    }

    // print channels
    if (xml->poses) {
        int i, j;
        int use_tag = 0;

        //channel use 使用している場合
        for (i = 0; i < xml->poses->size; i++) {

            if (xml->poses->pos[i]->channelsUse == USE_TAG) {
                sprintf(buf, "    <channels use=\"");
                pushStream(&result, buf);

                for (j = 0; j < xml->poses->pos[i]->numChannels; j++) {
                    sprintf(buf, "%d;", xml->poses->pos[i]->channels[j]);
                    pushStream(&result, buf);
                }

                sprintf(buf, "\">\n");
                pushStream(&result, buf);
                use_tag = 1;

                break;
            }
        }

        //個別id の channels
        for (i = 0; i < xml->poses->size; i++) {
            if (xml->poses->pos[i]->channelsUse == ID_TAG) {

                sprintf(buf, "        <channel id=\"%d\" use=\"", xml->poses->pos[i]->id);
                pushStream(&result, buf);

                for (j = 0; j < xml->poses->pos[i]->numChannels; j++) {
                    sprintf(buf, "%d;", xml->poses->pos[i]->channels[j]);
                    pushStream(&result, buf);
                }

                sprintf(buf, "\"/>\n");
                pushStream(&result, buf);
            }
        }

        if (use_tag == 1) {
            sprintf(buf, "    </channels>\n");
            pushStream(&result, buf);
        }

    }

    sprintf(buf, "</hark_xml>\n");
    pushStream(&result, buf);

    if (fstream != NULL) {
        fprintf(fstream, "%s", result);
        free(result);
    } else {
        *cstream = result;
    }

    return EXIT_SUCCESS;
}

/*!
 * @brief 内部処理用関数
 */
int harkio_XML_internal_writePositions(harkio_Positions* p, FILE *fstream, char **cstream) {
    assert(p);

    char *result = NULL;
    char buf[65535];

    harkio_XML_internal_writePositions__(p, buf, &result);

    if (fstream != NULL) {
        fprintf(fstream, "%s", result);
        free(result);
    } else {
        *cstream = result;
    }

    return EXIT_SUCCESS;
}

/*!
 * @brief 内部処理用関数
 */
int harkio_XML_internal_processNode(xmlTextReaderPtr reader, harkio_XML *xml) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    static enum harkio_XML_status currentState = STATE_NONE;
    static enum Coordinate sys;
//    int nodeType;
    xmlReaderTypes nodeType;
    xmlChar *name, *value;
    xmlChar *name1;
    const char *constValue;

    name1 = xmlTextReaderName(reader);
    nodeType = xmlTextReaderNodeType(reader);

    if (nodeType == XML_READER_TYPE_ELEMENT) {
        if (xmlStrcmp(name1, BAD_CAST "config") == 0) {
            currentState = STATE_CONFIG;
            xml->cfg = harkio_Config_new();
        } else if (xmlStrcmp(name1, BAD_CAST "comment") == 0) {
            currentState = STATE_CFG_COMMENT;
        } else if (xmlStrcmp(name1, BAD_CAST "SynchronousAverage") == 0) {
            currentState = STATE_CFG_SA;
        } else if (xmlStrcmp(name1, BAD_CAST "TSPpath") == 0) {
            currentState = STATE_CFG_TSPpath;
        } else if (xmlStrcmp(name1, BAD_CAST "TSPOffset") == 0) {
            currentState = STATE_CFG_TSPOffset;
        } else if (xmlStrcmp(name1, BAD_CAST "PeakSearch") == 0) {
            currentState = STATE_CFG_PeakSearch;
            xmlTextReaderMoveToFirstAttribute(reader);
            do {
                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char*) value;

                if (xmlStrcmp(name, BAD_CAST "from") == 0) {
                    harkio_Config_setPeakSearchFrom(xml->cfg, atoi(constValue));
                } else if (xmlStrcmp(name, BAD_CAST "to") == 0) {
                    harkio_Config_setPeakSearchTo(xml->cfg, atoi(constValue));
                }
                xmlFree(name);
                xmlFree(value);
            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
        } else if (xmlStrcmp(name1, BAD_CAST "nfft") == 0) {
            currentState = STATE_CFG_nfft;
        } else if (xmlStrcmp(name1, BAD_CAST "samplingRate") == 0) {
            currentState = STATE_CFG_samplingRate;
        }

        else if (xmlStrcmp(name1, BAD_CAST "signalMax") == 0) {
            currentState = STATE_CFG_signalMax;
        }

        else if (xmlStrcmp(name1, BAD_CAST "TSPLength") == 0) {
            currentState = STATE_CFG_TSPLength;
        } else if (xmlStrcmp(name1, BAD_CAST "positions") == 0) {

            currentState = STATE_POSITIONS;
            xml->poses = harkio_Positions_new(NULL, -1, 0, NULL);
            xmlTextReaderMoveToFirstAttribute(reader);

            do {

                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char*) value;

                if (xmlStrcmp(name, BAD_CAST "type") == 0) {
                    xml->poses->type = (char*) malloc(sizeof(char) * (strlen(constValue) + 1));
                    strcpy(xml->poses->type, constValue);
                } else if (xmlStrcmp(name, BAD_CAST "coordinate") == 0) {
                    if (xmlStrcmp(value, BAD_CAST "cartesian") == 0) {
                        sys = Cartesian;
                    } else {
                        xmlFree(name);
                        xmlFree(value);
                        return EXIT_FAILURE;
                    }
                }
                xmlFree(name);
                xmlFree(value);
            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
        } else if (xmlStrcmp(name1, BAD_CAST "position") == 0) {

            int id;
            float coord[3] = { 0, 0, 0 };
            char *path = NULL;

            xmlTextReaderMoveToFirstAttribute(reader);
            do {
                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char*) value;
                // fprintf(stderr, "%s=%s\n", name, value);

                if (xmlStrcmp(name, BAD_CAST "x") == 0) {
                    coord[0] = atof(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "y") == 0) {
                    coord[1] = atof(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "z") == 0) {
                    coord[2] = atof(constValue);
                }
                if (xmlStrcmp(name, BAD_CAST "azimuth") == 0) {
                    coord[0] = atof(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "elevation") == 0) {
                    coord[1] = atof(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "radius") == 0) {
                    coord[2] = atof(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "path") == 0) {
                    if (path != NULL) {
                        harkio_Log_printf(harkio_Log_Error, "XML position duplicate path exist");
                    }
                    path = (char *) malloc(sizeof(char) * 4192);
                    strncpy(path, constValue, 4192);
                } else if (xmlStrcmp(name, BAD_CAST "id") == 0) {
                    id = atoi(constValue);
                }
                xmlFree(name);
                xmlFree(value);

            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
            harkio_Positions_append(xml->poses, harkio_Position_new(id, sys, coord, path));
            free(path);
        } else if (xmlStrcmp(name1, BAD_CAST "neighbors") == 0) {
            currentState = STATE_NEIGHBORS;
            xml->nbrs = harkio_Neighbors_new(NULL);

            // get the calculation algorithm
            xmlTextReaderMoveToFirstAttribute(reader);
            name = xmlTextReaderName(reader);
            value = xmlTextReaderValue(reader);
            if (xmlStrcmp(value, BAD_CAST "NearestNeighbor") == 0) {
                xml->nbrs->alg = NearestNeighbor;
            } else {
                xml->nbrs->alg = Undefined;
            }

            xmlFree(name);
            xmlFree(value);
        } else if (xmlStrcmp(name1, BAD_CAST "neighbor") == 0) {
            int id;
            int numNeighbors;
            int *neighbors = NULL;

            xmlTextReaderMoveToFirstAttribute(reader);
            do {
                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char *) value;

                if (xmlStrcmp(name, BAD_CAST "id") == 0) {
                    id = atoi(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "ids") == 0) {
                    int i;
                    char *pointer;

                    // count number of neighbors
                    numNeighbors = harkio_XML_internal_strcount(constValue, ';');
                    if (neighbors != NULL) {
                        harkio_Log_printf(harkio_Log_Error, "XML neighbor duplicate ids exist");
                    }
                    neighbors = (int *) malloc(sizeof(int) * numNeighbors);

                    // get each neighbors;
                    pointer = strtok((char *) value, ";");
                    neighbors[0] = atoi((const char*) pointer);
                    for (i = 1; i < numNeighbors; i++) {
                        pointer = strtok(NULL, ";");
                        neighbors[i] = atoi((const char*) pointer);
                    }
                }
                xmlFree(name);
                xmlFree(value);

            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
            harkio_Neighbors_append(xml->nbrs, id, numNeighbors, neighbors);
            free(neighbors);
        } else if (xmlStrcmp(name1, BAD_CAST "channels") == 0) {

            int numChannels;
            int *channels = NULL;
            int *tmp = NULL;

            currentState = STATE_CHANNELS;

            xmlTextReaderMoveToFirstAttribute(reader);

            do {

                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char*) value;

                if (xmlStrcmp(name, BAD_CAST "use") == 0) {
                    int i, j;
                    char *pointer;

                    // count number of channels use
                    numChannels = harkio_XML_internal_strcount(constValue, ';');

                    tmp = (int *) malloc(sizeof(int) * numChannels);

                    // get each channels;
                    pointer = strtok((char *) value, ";");
                    tmp[0] = atoi((const char*) pointer);

                    for (j = 1; j < numChannels; j++) {
                        pointer = strtok(NULL, ";");
                        tmp[j] = atoi((const char*) pointer);

                    }

                    //全positionにセットする
                    for (i = 0; i < xml->poses->size; i++) {

                        harkio_Position_setChannels(xml->poses->pos[i], tmp, numChannels);
                        harkio_Position_setNumChannels(xml->poses->pos[i], numChannels);
                        harkio_Position_setChannelsUse(xml->poses->pos[i], USE_TAG);
                    }

                    free(tmp);
                }
                xmlFree(name);
                xmlFree(value);

            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
        }

        else if (xmlStrcmp(name1, BAD_CAST "channel") == 0) {

            int id;
            int numChannels;
            int *channel = NULL;

            xmlTextReaderMoveToFirstAttribute(reader);

            do {
                name = xmlTextReaderName(reader);
                value = xmlTextReaderValue(reader);
                constValue = (const char*) value;
                // fprintf(stderr, "%s=%s\n", name, value);

                if (xmlStrcmp(name, BAD_CAST "id") == 0) {
                    id = atoi(constValue);
                } else if (xmlStrcmp(name, BAD_CAST "use") == 0) {
                    int i, j;
                    char *pointer;

                    // count number of channel
                    numChannels = harkio_XML_internal_strcount(constValue, ';');
                    channel = (int *) malloc(sizeof(int) * numChannels);

                    // get each channel;
                    pointer = strtok((char *) value, ";");
                    channel[0] = atoi((const char*) pointer);

                    for (i = 1; i < numChannels; i++) {
                        pointer = strtok(NULL, ";");
                        channel[i] = atoi((const char*) pointer);
                    }

                    for (j = 0; j < xml->poses->size; j++) {
                        if (xml->poses->pos[j]->id == id) {
                            harkio_Position_setChannels(xml->poses->pos[j], channel, numChannels);
                            harkio_Position_setNumChannels(xml->poses->pos[j], numChannels);
                            harkio_Position_setChannelsUse(xml->poses->pos[j], ID_TAG);

                            break;
                        }
                    }

                }

            } while (xmlTextReaderMoveToNextAttribute(reader) == 1);
        }

    } else if (nodeType == XML_READER_TYPE_TEXT) {
        value = xmlTextReaderValue(reader);
        constValue = (const char *) value;
        switch (currentState) {
        case STATE_CFG_SA:
            harkio_Config_setSynchronousAverage(xml->cfg, atoi(constValue));
            break;
        case STATE_CFG_COMMENT:
            harkio_Config_setComment(xml->cfg, constValue);
            break;
        case STATE_CFG_TSPpath:
            harkio_Config_setTSPpath(xml->cfg, constValue);
            break;
        case STATE_CFG_TSPOffset:
            harkio_Config_setTSPoffset(xml->cfg, atoi(constValue));
            break;
        case STATE_CFG_nfft:
            harkio_Config_setNfft(xml->cfg, atoi(constValue));
            break;
        case STATE_CFG_samplingRate:
            harkio_Config_setSamplingRate(xml->cfg, atoi(constValue));
            break;
        case STATE_CFG_signalMax:
            harkio_Config_setSignalMax(xml->cfg, atoi(constValue));
            break;
        case STATE_CFG_TSPLength:
            harkio_Config_setTSPlength(xml->cfg, atoi(constValue));
            break;
        default:
            break;
        }
        xmlFree(value);
    } else if (nodeType == XML_READER_TYPE_END_ELEMENT) {
        currentState = STATE_NONE;
    }
    xmlFree(name1);

    name1 = xmlTextReaderName(reader);

    xmlFree(name1);
    return EXIT_SUCCESS;
}

/*!
 * @brief 内部処理用関数
 */
int harkio_XML_internal_strcount(const char *str, char key) {
    harkio_Log_printf(harkio_Log_Trace, "@ %s", __func__);

    int count = 0;
    int i;
    for (i = 0; i < strlen(str); i++) {
        if (str[i] == key)
            count++;
    }
    return count;
}

/*!
 * @brief 内部処理用関数
 */
void pushStream(char **buffer, char *tmp) {
    if (*buffer == NULL) {
        *buffer = (char *) malloc(strlen(tmp) + 1);
        strcpy(*buffer, tmp);
    } else {
        *buffer = (char *) realloc(*buffer, sizeof(char) * (strlen(*buffer) + strlen(tmp) + 1));
        strncat(*buffer, tmp, strlen(tmp));
    }
}
