//=============================================================================================================
/**
 * @file     mne_fiff_exp_set.cpp
 * @author   Christoph Dinh <chdinh@nmr.mgh.harvard.edu>;
 *           Lorenz Esch <lesch@mgh.harvard.edu>
 * @version  dev
 * @date     December, 2016
 *
 * @section  LICENSE
 *
 * Copyright (C) 2016, Christoph Dinh, Lorenz Esch. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that
 * the following conditions are met:
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *       the following disclaimer in the documentation and/or other materials provided with the distribution.
 *     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
 *       to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * @brief    Definition of the MneFiffExpSet class.
 *
 */


//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "mne_fiff_exp_set.h"
#include "mne_show_fiff_settings.h"


#include <fiff/fiff_tag.h>
#include <fiff/fiff_stream.h>

#include <iostream>


//*************************************************************************************************************
//=============================================================================================================
// Qt INCLUDES
//=============================================================================================================

#include <QFile>
#include <QDateTime>
#include <QDebug>
#include <QRegularExpression>
#include <algorithm>


//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace SHOWFIFF;
using namespace FIFFLIB;


//*************************************************************************************************************
//=============================================================================================================
// Static Definitions
//=============================================================================================================

#define LONG_LINE  80

#define CLASS_TAG     1
#define CLASS_BLOCK   2
#define CLASS_UNIT    3
#define CLASS_UNITM   4
#define CLASS_CH_KIND 5


//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

MneFiffExpSet::MneFiffExpSet()
{
}


//*************************************************************************************************************

MneFiffExpSet::MneFiffExpSet(const MneFiffExpSet &p_MneFiffExpSet)
: m_qListExp(p_MneFiffExpSet.m_qListExp)
{

}


//*************************************************************************************************************

MneFiffExpSet::~MneFiffExpSet()
{

}


//*************************************************************************************************************

const MneFiffExp& MneFiffExpSet::operator[] (int idx) const
{
    if (idx >= m_qListExp.length())
    {
        qWarning("Warning: Required MneFiffExp doesn't exist! Returning MneFiffExp '0'.");
        idx=0;
    }
    return m_qListExp[idx];
}


//*************************************************************************************************************

MneFiffExp& MneFiffExpSet::operator[] (int idx)
{
    if (idx >= m_qListExp.length())
    {
        qWarning("Warning: Required MneFiffExp doesn't exist! Returning MneFiffExp '0'.");
        idx = 0;
    }
    return m_qListExp[idx];
}


//*************************************************************************************************************

MneFiffExpSet &MneFiffExpSet::operator<<(const MneFiffExp &p_MneFiffExp)
{
    this->m_qListExp.append(p_MneFiffExp);
    return *this;
}


//*************************************************************************************************************

MneFiffExpSet MneFiffExpSet::read_fiff_explanations(const QString &name)
{
    QFile file(name);

    MneFiffExpSet res;

    int         exclass,kind;
    QString     text;

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical() << name;
        return res;
    }

    QTextStream in(&file);
    bool ok = false;
    while (!in.atEnd()) {
        QString line = in.readLine();

        QStringList list = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);

        if(list.size() < 3)
            continue;

        exclass = list[0].toInt(&ok);
        if(!ok)
            continue;

        kind = list[1].toInt(&ok);
        if(!ok)
            break;

        text.clear();

        text = list[2];
        for(int i = 3; i < list.size(); ++i)
            text += " " + list[i];

        text.remove("\"");

        MneFiffExp exp;
        exp.exclass = exclass;
        exp.kind = kind;
        exp.text = text;

        res << exp;
    }


    if (res.size() == 0) {
        qCritical("No explanations in %s",name.toUtf8().constData());
        return res;
    }

    res.sort_fiff_explanations();

    return res;
}


//*************************************************************************************************************

void MneFiffExpSet::list_fiff_explanations(FILE *out)
{
    for (int k = 0; k < this->size(); k++)
        fprintf(out,"%d %d \"%s\"\n",this->m_qListExp[k].exclass,this->m_qListExp[k].kind,this->m_qListExp[k].text.toUtf8().constData());
}


//*************************************************************************************************************

QList<MneFiffExp>::const_iterator MneFiffExpSet::find_fiff_explanation(int exclass, int kind) const
{
    MneFiffExp one;
    one.exclass = exclass;
    one.kind  = kind;

    auto it = std::lower_bound(this->m_qListExp.begin(), this->m_qListExp.end(), one, MneFiffExp::comp_exp);
    if (it != this->m_qListExp.end() && !MneFiffExp::comp_exp(one, *it)) {
        return it;
    }
    return this->m_qListExp.end();
}


//*************************************************************************************************************

QList<MneFiffExp>::const_iterator MneFiffExpSet::constEnd() const
{
    return m_qListExp.constEnd();
}


//*************************************************************************************************************

//*************************************************************************************************************

void MneFiffExpSet::print_file_id (FILE *out, FiffTag::SPtr tag)
{
    FiffId id = tag->toFiffID();
    struct tm *ltime;
    time_t time_val;
    char buf[100];

    fprintf(out,"\t%d.%d ",id.version>>16,id.version & 0xFFFF);
    fprintf(out,"0x%x%x ",id.machid[0],id.machid[1]);

    time_val = id.time.secs;
    ltime = localtime(&time_val);
    (void)strftime(buf,100,"%c",ltime);

    fprintf(out,"%s",buf);
}


//*************************************************************************************************************

void MneFiffExpSet::print_ch_info (FILE *out, FiffTag::SPtr tag)
{
    FiffChInfo info = tag->toChInfo();
    QList<MneFiffExp>::const_iterator exp;
    QString text1, text2, text3;

    exp = this->find_fiff_explanation(CLASS_UNIT, info.unit);
    text1 = (exp != this->constEnd()) ? exp->text : "N/A";

    exp = this->find_fiff_explanation(CLASS_UNITM, info.unit_mul);
    text2 = (exp != this->constEnd()) ? exp->text : "";

    exp = this->find_fiff_explanation(CLASS_CH_KIND, info.kind);
    text3 = (exp != this->constEnd()) ? exp->text : "N/A";

    fprintf(out,"\t%3d \"%s\" (%s %3d) [%s%s]", info.scanNo, info.ch_name.toUtf8().constData(), text3.toUtf8().constData(), info.logNo, text2.toUtf8().constData(), text1.toUtf8().constData());
    fprintf(out,"\trange %g cal %g", info.range, info.cal);
}


//*************************************************************************************************************

void MneFiffExpSet::print_transform(FILE *out, FiffTag::SPtr tag)
{
    FiffCoordTrans t = tag->toCoordTrans();
    int k, p;
    int frame;
    fprintf(out,"\t");
    for (frame = t.from, k = 0; k < 2; k++) {
        fprintf(out,"%s", FIFFLIB::FiffCoordTrans::frame_name(frame).toUtf8().constData());
        if (k == 0) {
            fprintf(out," -> ");
            frame = t.to;
        }
        else {
            fprintf(out,"\n");
            for (p = 0; p < 3; p++)
                fprintf(out,"%24s\t%8.5f %8.5f %8.5f %8.2f\n"," ",t.trans(p,0),t.trans(p,1),t.trans(p,2),1000*t.trans(p,3));
            fprintf(out,"%24s\t%8.5f %8.5f %8.5f %8.2f"," ",0.0,0.0,0.0,1.0);
        }
    }
}


//*************************************************************************************************************

void MneFiffExpSet::print_dig_point(FILE *out, FiffTag::SPtr tag)
{
    FiffDigPoint point = tag->toDigPoint();
    switch (point.kind) {
    case FIFFV_POINT_CARDINAL :
        fprintf(out,"\tcardinal %3d ",point.ident);
        break;
    case FIFFV_POINT_HPI :
        fprintf(out,"\thpi      %3d ",point.ident);
        break;
    case FIFFV_POINT_EEG :
        fprintf(out,"\teeg      %3d ",point.ident);
        break;
    case FIFFV_POINT_EXTRA :
        fprintf(out,"\textra    %3d ",point.ident);
        break;
    }
    fprintf(out,"(%6.1f,%6.1f,%6.1f)",1000*point.r[0],1000*point.r[1], 1000*point.r[2]);
}


//*************************************************************************************************************

void MneFiffExpSet::print_matrix(FILE *out, FiffStream::SPtr stream, FiffDirEntry::SPtr this_ent)
{
    // Simplified matrix info since we don't want to read the whole tag yet if it's large,
    // but FiffStream has some matrix reading logic.
    fprintf(out, "\tMatrix type %d, size %d bytes", this_ent->type, this_ent->size);
}


//*************************************************************************************************************

bool MneFiffExpSet::show_fiff_contents(FILE *out, const MneShowFiffSettings &settings)
{
    return show_fiff_contents(out,settings.inname, settings.verbose,settings.tags,settings.indent,settings.long_strings,settings.blocks_only);
}


//*************************************************************************************************************

bool MneFiffExpSet::show_fiff_contents(FILE *out, const QString &name, bool verbose, const QList<int> &tags, int indent_step, bool long_strings, bool blocks_only)
{
    QFile file(name);
    FiffStream::SPtr stream(new FiffStream(&file));

    FiffDirEntry::SPtr this_ent;
    FiffTag::SPtr   tag;
    int             day,month,year;
    int             block;
    int             count = 0;
    int             prev_kind;
    int             indent = 0;
    bool            show_it = false;
    QString         s;
    bool            output_taginfo = false;
    QList<MneFiffExp>::const_iterator exp;

    if (!stream->open())
        return false;
    prev_kind = -1;

    if (blocks_only) {
        //        for (auto this_ent : stream->dir()) {//C++11
        for (int i = 0; i < stream->dir().size(); ++i) {
            this_ent = stream->dir()[i];
            if (this_ent->kind == FIFF_BLOCK_START || this_ent->kind == FIFF_BLOCK_END) {
                if (this_ent->kind == FIFF_BLOCK_END)
                    indent = indent - indent_step;
                if (this_ent->kind == FIFF_BLOCK_START) {
                    for (int k = 0; k < indent; k++)
                        fprintf(out," ");
                    if ( stream->read_tag(tag, this_ent->pos) ) {
                        block = *tag->toInt();
                        exp = this->find_fiff_explanation(CLASS_BLOCK,block);
                        if (exp != this->constEnd())
                            fprintf(out,"%-d = %-s\n",exp->kind,exp->text.toUtf8().constData());
                        else
                            fprintf(out,"%-d = %-s\n",block,"Not explained");
                    }
                }
                if (this_ent->kind == FIFF_BLOCK_START)
                    indent = indent + indent_step;
            }
        }
    }
    else {
        //        for (auto this_ent : stream->dir()) {//C++11
        for (int i = 0; i < stream->dir().size(); ++i) {
            this_ent = stream->dir()[i];
            if (this_ent->kind == -1) {
            }
            if (tags.size() == 0)
                show_it = true;
            else {
                show_it = false;
                for (int k = 0; k < tags.size(); k++) {
                    if (this_ent->kind == tags[k]) {
                        show_it = true;
                        break;
                    }
                }
            }
            if (show_it) {
                if (this_ent->kind == FIFF_BLOCK_START || this_ent->kind == FIFF_BLOCK_END) {
                    if (!verbose) {
                        if (count > 1)
                            fprintf(out," [%d]\n",count);
                        else if (this_ent != stream->dir()[0])
                            fprintf(out,"\n");
                    }
                    if (this_ent->kind == FIFF_BLOCK_END)
                        indent = indent - indent_step;
                    for (int k = 0; k < indent; k++)
                        fprintf(out," ");
                    exp = this->find_fiff_explanation(CLASS_TAG,this_ent->kind);
                    if (exp != this->constEnd())
                        fprintf(out,"%4d = %-s",exp->kind,exp->text.toUtf8().constData());
                    else
                        fprintf(out,"%4d = %-s",this_ent->kind,"Not explained");
                    if ( stream->read_tag(tag,this_ent->pos)) {
                        block = *tag->toInt();
                        exp = this->find_fiff_explanation(CLASS_BLOCK,block);
                        if (exp != this->constEnd())
                            fprintf(out,"\t%-d = %-s",exp->kind,exp->text.toUtf8().constData());
                        else
                            fprintf(out,"\t%-d = %-s",block,"Not explained");
                    }
                    if ( this_ent->kind == FIFF_BLOCK_START)
                        indent = indent + indent_step;
                    count = 1;
                    if (verbose)
                        fprintf(out,"\n");
                }
                else if (verbose) {
                    for (int k = 0; k < indent; k++)
                        fprintf(out," ");
                    if (output_taginfo) {
                        fprintf(out,"%d %d ",this_ent->size,this_ent->type);
                    }
                    exp = this->find_fiff_explanation(CLASS_TAG,this_ent->kind);
                    if (exp != this->constEnd())
                        fprintf(out,"%4d = %-18s",exp->kind,exp->text.toUtf8().constData());
                    else
                        fprintf(out,"%4d = %-18s",this_ent->kind,"Not explained");
                    if (FiffTag::fiff_type_fundamental(this_ent->type) == FIFFTS_FS_MATRIX) {
                        this->print_matrix(out, stream, this_ent);
                    }
                    else {
                        switch (this_ent->type) {
                        case FIFFT_INT :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                if (this_ent->kind == FIFF_BLOCK_START ||
                                        this_ent->kind == FIFF_BLOCK_END) {
                                    block = *tag->toInt();
                                    exp = this->find_fiff_explanation(CLASS_BLOCK,block);
                                    if (exp != this->constEnd())
                                        fprintf(out,"\t%-d = %s",exp->kind,exp->text.toUtf8().constData());
                                    else
                                        fprintf(out,"\t%-d = %-s",block,"Not explained");
                                }
                                else if (this_ent->kind == FIFF_MEAS_DATE) {
                                    QDateTime ltime;
                                    ltime.setSecsSinceEpoch(tag->toInt()[0]);
                                    fprintf(out,"\t%s",ltime.toString().toUtf8().constData());
                                }
                                else if (tag->size() == sizeof(fiff_int_t))
                                    fprintf(out,"\t%d",*tag->toInt());
                                else
                                    fprintf(out,"\t%d ints",(int)(tag->size()/sizeof(fiff_int_t)));
                            }
                            break;
                        case FIFFT_UINT :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                if (tag->size() == sizeof(fiff_int_t))
                                    fprintf(out,"\t%d",*tag->toUnsignedInt());
                                else
                                    fprintf(out,"\t%d u_ints",(int)(tag->size()/sizeof(fiff_int_t)));
                            }
                            break;
                        case FIFFT_JULIAN :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                // MNE-CPP doesn't seem to have a direct julian to caldate, skipping for now or simplistic print
                                fprintf(out,"\tJulian date tag");
                            }
                            break;
                        case FIFFT_STRING :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                s = tag->toString();
                                if (long_strings)
                                    fprintf(out,"\t%s",tag->toString().toUtf8().constData());
                                else {
                                    if ((s.indexOf("\n")) != -1)
                                        s.replace(s.indexOf("\n"), 2, "\0");
                                    else if (s.size() > LONG_LINE) {
                                        s.truncate(LONG_LINE);
                                        s += "...";
                                    }
                                    fprintf(out,"\t%s",s.toUtf8().constData());
                                }
                            }
                            break;
                        case FIFFT_FLOAT :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                if (tag->size() == sizeof(fiff_float_t))
                                    fprintf(out,"\t%g",*tag->toFloat());
                                else
                                    fprintf(out,"\t%d floats",(int)(tag->size()/sizeof(fiff_float_t)));
                            }
                            break;
                        case FIFFT_DOUBLE :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                if (tag->size() == sizeof(fiff_double_t))
                                    fprintf(out,"\t%g",*tag->toDouble());
                                else
                                    fprintf(out,"\t%d doubles",(int)(tag->size()/sizeof(fiff_double_t)));
                            }
                            break;
                        case FIFFT_COMPLEX_FLOAT :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                float *fdata = tag->toFloat();
                                if (tag->size() == 2*sizeof(fiff_float_t))
                                    fprintf(out,"\t(%g %g)",fdata[0],fdata[1]);
                                else
                                    fprintf(out,"\t%d complex numbers",
                                            (int)(tag->size()/(2*sizeof(fiff_float_t))));
                            }
                            break;
                        case FIFFT_COMPLEX_DOUBLE :
                            if (stream->read_tag(tag,this_ent->pos)) {
                                double *ddata = tag->toDouble();
                                if (tag->size() == 2*sizeof(fiff_double_t))
                                    fprintf(out,"\t(%g %g)",ddata[0],ddata[1]);
                                else
                                    fprintf(out,"\t%d double complex numbers",
                                            (int)(tag->size()/(2*sizeof(fiff_double_t))));
                            }
                            break;
                        case FIFFT_CH_INFO_STRUCT :
                            if (stream->read_tag(tag,this_ent->pos))
                                this->print_ch_info(out, tag);
                            break;
                        case FIFFT_ID_STRUCT :
                            if (stream->read_tag(tag,this_ent->pos))
                                this->print_file_id(out, tag);
                            break;
                        case FIFFT_DIG_POINT_STRUCT :
                            if (stream->read_tag(tag,this_ent->pos))
                                this->print_dig_point(out, tag);
                            break;
                        case FIFFT_DIG_STRING_STRUCT :
                            if (stream->read_tag(tag,this_ent->pos)) {
#ifdef FOO
                                if ((ds = decode_fiff_dig_string(&tag)) != NULL)
                                    print_dig_string (ds);
                                free_fiff_dig_string(ds);
#endif
                            }
                            break;
                        case FIFFT_COORD_TRANS_STRUCT :
                            if (stream->read_tag(tag,this_ent->pos))
                                this->print_transform(out, tag);
                            break;
                        default :
                            if (this_ent->kind == FIFF_DIG_STRING)
                                fprintf(out,"type = %d\n",this_ent->type);
                            if (this_ent->size > 0)
                                fprintf(out,"\t%d bytes",this_ent->size);
                            break;
                        }
                    }
                    fprintf(out,"\n");
                    prev_kind = this_ent->kind;
                }
                else {
                    if (this_ent->kind != prev_kind) {
                        if (count > 1)
                            fprintf(out," [%d]\n",count);
                        else if (this_ent != stream->dir()[0])
                            fprintf(out,"\n");
                        for (int k = 0; k < indent; k++)
                            fprintf(out," ");
                        exp = this->find_fiff_explanation(CLASS_TAG,this_ent->kind);
                        if (exp != this->constEnd())
                            fprintf(out,"%4d = %-s",exp->kind,exp->text.toUtf8().constData());
                        else
                            fprintf(out,"%4d = %-s",this_ent->kind,"Not explained");
                        count = 1;
                    }
                    else
                        count++;
                }
            }
            prev_kind = this_ent->kind;
        }
        if (!verbose) {
            if (count > 1)
                fprintf(out," [%d]\n",count);
            else
                fprintf(out,"\n");
        }
    }

    stream->close();

    return true;
}



//*************************************************************************************************************

void MneFiffExpSet::sort_fiff_explanations()
{
    if (this->size() == 0)
        return;

    std::sort(this->m_qListExp.begin(), this->m_qListExp.end(), MneFiffExp::comp_exp);

    return;
}
