/****************************************************************************
**  SCALASCA    http://www.scalasca.org/                                   **
*****************************************************************************
**  Copyright (c) 1998-2012                                                **
**  Forschungszentrum Juelich GmbH, Juelich Supercomputing Centre          **
**                                                                         **
**  Copyright (c) 2003-2008                                                **
**  University of Tennessee, Innovative Computing Laboratory               **
**                                                                         **
**  See the file COPYRIGHT in the package base directory for details       **
****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "esd_paths.h"
#include "esd_unify.h"
#include "epk_archive.h"
#include "epk_vector.h"
#include "elg_error.h"
#include "cube.h"

#define VERSION "1.4"
#define PATTERNS_URL "@mirror@scalasca_patterns-" VERSION ".html#"

/* number of performance metrics (excluding time) */
EXTERN int esd_num_metrics;

static struct
{
    cube_t*    cube;
    char*      filename;
    FILE*      fp;
    EpkVector* metricv;
    EpkVector* cpathv;
} EsdReport;


/*
    Prepare analysis report.
    Use filename if provided, otherwise generate default name.
    Return 0 is fail to open report or create cube object.
*/
int esd_report_open (char* filename)
{
    if (!filename) {
        static char* cubefile = "epitome.cube";
        filename = malloc(256);
        sprintf(filename, "%s/%s", epk_archive_get_name(), cubefile);
    }

    EsdReport.fp = fopen(filename, "w");
    if (EsdReport.fp == NULL) {
        elg_warning("Error opening analysis report %s: %s", filename, strerror(errno));
        return 0;
    } else
        elg_cntl_msg("Generating analysis report %s ...", filename);

    EsdReport.cube = cube_create();
    if (!EsdReport.cube) {
        elg_warning("cube_create failed!");
        return 0;
    }
    EsdReport.filename = filename;
    return 1;
}

/*
    Write unified definitions to cube analysis report.
*/
void esd_report_defs (EsdUnifyData* udata, int unknowns)
{
    int i;
    cube_t* cube = EsdReport.cube;

    /* define header */
    cube_def_mirror(cube, "http://www2.fz-juelich.de/jsc/datapool/scalasca/");
    cube_def_attr(cube, "CUBE_CT_AGGR", "NONE");
        
    /* generate metric definitions */
    EsdReport.metricv = epk_vector_create_size(2+epk_vector_size(udata->metrics));

    /* standard metrics */
    cube_metric* visits_metric = cube_def_met(cube, "Visits",
                "visits", "INTEGER", "occ", "",
                PATTERNS_URL "visits", "Number of visits", NULL);
    epk_vector_push_back(EsdReport.metricv, visits_metric);
#ifdef MSG_HACK /* hack for message statistics */
    /* synchronizations */
    cube_metric* syncs_coll_metric = cube_def_met(cube, "Collective synchronizations",
                "syncs_coll", "INTEGER", "occ", "", PATTERNS_URL "syncs_coll",
                "Number of collective synchronization operations", NULL);
    epk_vector_push_back(EsdReport.metricv, syncs_coll_metric);
    cube_metric* syncs_send_metric = cube_def_met(cube, "P2P send synchronizations",
                "syncs_send", "INTEGER", "occ", "", PATTERNS_URL "syncs_send",
                "Number of point-to-point send synchronization operations", NULL);
    epk_vector_push_back(EsdReport.metricv, syncs_send_metric);
    cube_metric* syncs_recv_metric = cube_def_met(cube, "P2P recv synchronizations",
                "syncs_recv", "INTEGER", "occ", "", PATTERNS_URL "syncs_recv",
                "Number of point-to-point receive synchronization operations", NULL);
    epk_vector_push_back(EsdReport.metricv, syncs_recv_metric);

    /* communications */
    cube_metric* comms_cxch_metric = cube_def_met(cube, "Collective exchange communications",
                "comms_cxch", "INTEGER", "occ", "", PATTERNS_URL "comms_cxch",
                "Number of collective communications as source and destination", NULL);
    epk_vector_push_back(EsdReport.metricv, comms_cxch_metric);
    cube_metric* comms_csrc_metric = cube_def_met(cube, "Collective communications as source",
                "comms_csrc", "INTEGER", "occ", "", PATTERNS_URL "comms_csrc",
                "Number of collective communications as source", NULL);
    epk_vector_push_back(EsdReport.metricv, comms_csrc_metric);
    cube_metric* comms_cdst_metric = cube_def_met(cube, "Collective communications as destination",
                "comms_cdst", "INTEGER", "occ", "", PATTERNS_URL "comms_cdst",
                "Number of collective communications as destination", NULL);
    epk_vector_push_back(EsdReport.metricv, comms_cdst_metric);
    cube_metric* comms_send_metric = cube_def_met(cube, "P2P send communications",
                "comms_send", "INTEGER", "occ", "", PATTERNS_URL "comms_send",
                "Number of point-to-point send communication operations", NULL);
    epk_vector_push_back(EsdReport.metricv, comms_send_metric);
    cube_metric* comms_recv_metric = cube_def_met(cube, "P2P recv communications",
                "comms_recv", "INTEGER", "occ", "", PATTERNS_URL "comms_recv",
                "Number of point-to-point receive communication operations", NULL);
    epk_vector_push_back(EsdReport.metricv, comms_recv_metric);

    /* bytes transferred */
    cube_metric* bytes_cout_metric = cube_def_met(cube, "Collective bytes outgoing",
                "bytes_cout", "INTEGER", "bytes", "", PATTERNS_URL "bytes_cout",
                "Number of bytes outgoing in collective operations", NULL);
    epk_vector_push_back(EsdReport.metricv, bytes_cout_metric);
    cube_metric* bytes_cin_metric = cube_def_met(cube, "Collective bytes incoming",
                "bytes_cin", "INTEGER", "bytes", "", PATTERNS_URL "bytes_cin",
                "Number of bytes incoming in collective operations", NULL);
    epk_vector_push_back(EsdReport.metricv, bytes_cin_metric);
#if 0
    cube_metric* bytes_copy_metric = cube_def_met(cube, "Collective bytes copied",
                "bytes_copy", "INTEGER", "bytes", "", PATTERNS_URL "bytes_copy",
                "Number of bytes copied in collective operations", NULL);
    epk_vector_push_back(EsdReport.metricv, bytes_copy_metric);
#endif
    cube_metric* bytes_sent_metric = cube_def_met(cube, "P2P bytes sent",
                "bytes_sent", "INTEGER", "bytes", "", PATTERNS_URL "bytes_sent", 
                "Number of bytes sent in point-to-point operations", NULL);
    epk_vector_push_back(EsdReport.metricv, bytes_sent_metric);
    cube_metric* bytes_rcvd_metric = cube_def_met(cube, "P2P bytes received",
                "bytes_rcvd", "INTEGER", "bytes", "", PATTERNS_URL "bytes_rcvd",
                "Number of bytes received in point-to-point operations", NULL);
    epk_vector_push_back(EsdReport.metricv, bytes_rcvd_metric);
    cube_metric* file_bytes_metric = cube_def_met(cube, "MPI file bytes transferred",
                "mpi_file_bytes", "INTEGER", "bytes", "", PATTERNS_URL "mpi_file_bytes",
                "Number of bytes transferred in MPI file operations", NULL);
    epk_vector_push_back(EsdReport.metricv, file_bytes_metric);
    /* XXXX OMP_HACK */
    cube_metric* tfork_metric = cube_def_met(cube, "OMP thread fork time",
                "omp_fork", "FLOAT", "sec", "", PATTERNS_URL "omp_fork",
                "OpenMP thread fork time", NULL);
    epk_vector_push_back(EsdReport.metricv, tfork_metric);
    cube_metric* tmgmt_metric = cube_def_met(cube, "OMP thread management time",
                "omp_management", "FLOAT", "sec", "", PATTERNS_URL "omp_management",
                "OpenMP thread management time", NULL);
    epk_vector_push_back(EsdReport.metricv, tmgmt_metric);
#endif /* MSG_HACK */
    cube_metric* ttime_metric = cube_def_met(cube, "Time",
                "time", "FLOAT", "sec", "", PATTERNS_URL "time",
                "Total CPU allocation time (includes time allocated for idle threads)", NULL);
    epk_vector_push_back(EsdReport.metricv, ttime_metric);

    /* add any hardware-counter metrics */
    elg_cntl_msg("metrics(%d)", epk_vector_size(udata->metrics));
    for (i=0; i<epk_vector_size(udata->metrics); i++) {
        UnifyMetric* umetric = epk_vector_at(udata->metrics, i);
        char* metricname = epk_vector_at(udata->strings, umetric->nameid);
        char* descname = epk_vector_at(udata->strings, umetric->descid);
        char* typename = (umetric->type == ELG_INTEGER) ? "INTEGER" : "FLOAT";
        char* unitname = (umetric->type == ELG_INTEGER) ? "occ" : "sec";
        elg_cntl_msg("metric[%d](%d)=\"%s\"(%d) desc=\"%s\" type=%d(%s) mode=%d ival=%d", i,
                umetric->id, metricname, umetric->nameid, descname,
                umetric->type, typename, umetric->mode, umetric->ival);
        cube_metric* parent = NULL;
        cube_metric* metric = cube_def_met(cube, metricname,
                metricname, typename, unitname, "", "", /* no URL for HWC metrics */
                descname, parent);
        epk_vector_push_back(EsdReport.metricv, metric);
    }

    /* generate region definitions */

    elg_cntl_msg("regions(%d)", epk_vector_size(udata->regions));
    EpkVector* regionv = epk_vector_create_size(epk_vector_size(udata->regions));
    for (i=0; i<epk_vector_size(udata->regions); i++) {
        UnifyRegion* uregion = epk_vector_at(udata->regions, i);
        elg_ui4 fileid = uregion->fileid;
        elg_ui4 descid = uregion->descid;
        char* regnname = epk_vector_at(udata->strings, uregion->nameid);
        elg_ui4* fnameid = (fileid == ELG_NO_ID) ? NULL : (elg_ui4*)epk_vector_at(udata->files, uregion->fileid);
        char* filename = (fnameid == NULL) ? NULL : epk_vector_at(udata->strings, *fnameid);
        char* descname = (descid == ELG_NO_ID) ? NULL : epk_vector_at(udata->strings, uregion->descid);
        if (strcmp(descname, "MPI")!=0) /* skip message */
        elg_cntl_msg("region[%d]=\"%s\"(%d) file=\"%s\"(%d) begln=%d endln=%d desc=\"%s\" type=%d", i,
                regnname, uregion->nameid, filename?filename:"", uregion->fileid,
                uregion->begln, uregion->endln, descname?descname:"", uregion->type);
        cube_region* region = cube_def_region(cube, regnname,
                uregion->begln, uregion->endln, "", /* no url */
                descname?descname:"", filename?filename:"");
        epk_vector_push_back(regionv, region);
    }


    /* generate machine definitions */
    elg_cntl_msg("machs(%d)", epk_vector_size(udata->machs));
    EpkVector* machinev = epk_vector_create_size(epk_vector_size(udata->machs));
    for (i=0; i<epk_vector_size(udata->machs); i++) {
        UnifyMach* umach = epk_vector_at(udata->machs, i);
        char* machname = epk_vector_at(udata->strings, umach->nameid);
        elg_cntl_msg("machine[%d](%d)=\"%s\"(%d) nodec=%d", i,
                umach->id, machname, umach->nameid, umach->nodec);
        cube_machine* machine = cube_def_mach(cube, machname, ""); /* no desc */
        epk_vector_push_back(machinev, machine);
    }

    /* generate node definitions */
    elg_cntl_msg("nodes(%d)", epk_vector_size(udata->nodes));
    EpkVector* nodev = epk_vector_create_size(epk_vector_size(udata->nodes));
    for (i=0; i<epk_vector_size(udata->nodes); i++) {
        UnifyNode* unode = epk_vector_at(udata->nodes, i);
        char* nodename = epk_vector_at(udata->strings, unode->nameid);
        cube_machine* machine = epk_vector_at(machinev, unode->machid);
        elg_cntl_msg("node[%d](%d)=\"%s\"(%d) machid=%d cpuc=%d clkrt=%g", i,
                unode->id, nodename, unode->nameid,
                unode->machid, unode->cpuc, unode->clockrt);
        cube_node* node = cube_def_node(cube, nodename, machine);
        epk_vector_push_back(nodev, node);
    }

    elg_cntl_msg("procs(%d)", epk_vector_size(udata->procs));
    EpkVector* processv = epk_vector_create_size(epk_vector_size(udata->procs));
#if 0
    /* generate process definitions (if any) */
    for (i=0; i<epk_vector_size(udata->procs); i++) {
        UnifyProc* uproc = epk_vector_at(udata->procs, i);
        char* procname = epk_vector_at(udata->strings, uproc->nameid);
        /* XXXX need to identify node from location */
        cube_node* node = epk_vector_at(nodev, 0);
        elg_cntl_msg("proc[%d](%d)=\"%s\"(%d)", i,
                uproc->id, procname, uproc->nameid);
        cube_process* process = cube_def_proc(cube, procname, uproc->id, node);
        epk_vector_push_back(processv, process);
    }
#endif

    elg_cntl_msg("thrds(%d)", epk_vector_size(udata->thrds));
    EpkVector* threadv = epk_vector_create_size(epk_vector_size(udata->thrds));
#if 0
    /* generate thread definitions (if any) */
    for (i=0; i<epk_vector_size(udata->thrds); i++) {
        UnifyThrd* uthrd = epk_vector_at(udata->thrds, i);
        char* thrdname = epk_vector_at(udata->strings, uthrd->nameid);
        cube_process* process = epk_vector_at(processv, uthrd->pid);
        elg_cntl_msg("thrd[%d](%d)=\"%s\"(%d) pid=%d", i,
                uthrd->id, thrdname, uthrd->nameid, uthrd->pid);
        cube_thread* thread = cube_def_thrd(cube, thrdname, uthrd->id, process);
        epk_vector_push_back(threadv, thread);
    }
#endif

    /* generate process and thread definitions from locations */
    elg_cntl_msg("locs(%d)", epk_vector_size(udata->locs));
    for (i=0; i<epk_vector_size(udata->locs); i++) {
        size_t index;
        UnifyLoc* uloc = epk_vector_at(udata->locs, i);
        elg_cntl_msg("loc[%d]: machid=%d nodeid=%d procid=%d thrdid=%d", i,
                uloc->machid, uloc->nodeid, uloc->procid, uloc->thrdid);
        if (uloc->procid >= epk_vector_size(processv)) { /* not defined yet */
            char* procname = ""; /* default name */
            /* check for process (name) definition */
            for (index=0; index < epk_vector_size(udata->procs); index++) {
                UnifyProc* uproc = epk_vector_at(udata->procs, index);
                if (uproc->id == uloc->procid) {
                    procname = epk_vector_at(udata->strings, uproc->nameid);
                    elg_cntl_msg("proc[%d](%d)=\"%s\"(%d)", index,
                                 uproc->id, procname, uproc->nameid);
                    break;
                }
            }
            cube_node*    node    = epk_vector_at(nodev, uloc->nodeid);
            cube_process* process = cube_def_proc(cube, procname, uloc->procid, node);
            epk_vector_push_back(processv, process);
        }
        char* thrdname = ""; /* default name */
        /* check for thread (name) definition */
        for (index=0; index < epk_vector_size(udata->thrds); index++) {
            UnifyThrd* uthrd = epk_vector_at(udata->thrds, index);
            if ((uthrd->id == uloc->thrdid) && (uthrd->pid == uloc->procid)) {
                thrdname = epk_vector_at(udata->strings, uthrd->nameid);
                elg_cntl_msg("thrd[%d](%d)=\"%s\"(%d) pid=%d", i,
                             uthrd->id, thrdname, uthrd->nameid, uthrd->pid);
                break;
            }
        }
        cube_process* process = epk_vector_at(processv, uloc->procid);
        cube_thread* thread = cube_def_thrd(cube, thrdname, uloc->thrdid, process);
        epk_vector_push_back(threadv, thread);
    }

    /* generate callpath definitions */
    elg_cntl_msg("cpaths(%d)", epk_vector_size(udata->cpaths));
    EsdReport.cpathv = epk_vector_create_size(epk_vector_size(udata->cpaths));
    elg_d8 last_order = 0;
    char* mod = ""; /*"UNKNOWN";*/
    elg_ui4 unknown = (unknowns) ? 0 : 1; /* parent cpath adjustment */
    for (i=0; i<epk_vector_size(udata->cpaths); i++) {
        UnifyCpath*  ucpath = epk_vector_at(udata->cpaths, i);
        elg_ui4 rid = ucpath->rid;
        cube_region* region = (rid == ELG_NO_ID) ? NULL : epk_vector_at(regionv, rid);
        elg_ui4 ppid = ucpath->ppid;
        cube_cnode*  parent = (ppid == ELG_NO_ID) ? NULL :
                epk_vector_at(EsdReport.cpathv, ppid-unknown);
        elg_d8 order;
        PUT_VALX(ucpath->order, order);
        if (order < last_order)
            elg_warning("cpath[%d]: rid=%d ppid=%d order=%.6f%s", i,
                    ucpath->rid, ucpath->ppid, order,
                    (order < last_order) ? " OutOfOrder" : "");
        last_order = order;
        if ((i==0) && !unknowns) continue; /* skip when not required */
        cube_cnode* cpath = cube_def_cnode_cs(cube, region, mod, 0, parent);
        epk_vector_push_back(EsdReport.cpathv, cpath);
    }
    elg_cntl_msg("defined %d cpaths", epk_vector_size(EsdReport.cpathv));

    /* generate topology definitions */
    elg_cntl_msg("tops(%d)", epk_vector_size(udata->tops));
    for (i=0; i<epk_vector_size(udata->tops); i++) {
      int j;
      cube_cartesian* cart;
      UnifyCart* ucart = epk_vector_at(udata->tops, i);

      /* convert data */
      long* dims   = (long*)malloc(ucart->ndims * sizeof(long));
      int*  period = (int*)malloc(ucart->ndims * sizeof(int));
      if (!dims || !period)
        elg_error();
      for (j = 0; j < ucart->ndims; j++) {
        dims[j]   = ucart->nlocs[j];
        period[j] = ucart->period[j];
      }
      cart = cube_def_cart(cube, ucart->ndims, dims, period);
      elg_cntl_msg("top[%d]: ndims=%d", i, ucart->ndims);

      /* generate coordinate definitions */
      elg_cntl_msg("coords(%d)", epk_vector_size(ucart->coords));
      for (j = 0; j<epk_vector_size(ucart->coords); j++) {
        int k;
        CartCoord* ucoord = epk_vector_at(ucart->coords, j);

        /* convert data */
        for (k = 0; k < ucart->ndims; k++)
          dims[k] = ucoord->coord[k];

        cube_def_coords(cube, cart, epk_vector_at(threadv, ucoord->locid), dims);
        elg_cntl_msg("coord[%d]: loc=%d", j, ucoord->locid);
      }

      free(dims);
      free(period);
    }

    /* generate definitions */
    cube_write_def(cube, EsdReport.fp);
}

/*
    Append row of (metric,cpath) severity values to analysis report.
*/
void esd_report_sev_row (int metricid, int cpathid, double* sevbuf)
{
    cube_metric* metric = epk_vector_at(EsdReport.metricv, metricid);
    cube_cnode*   cpath = epk_vector_at(EsdReport.cpathv, cpathid);

    cube_write_sev_row(EsdReport.cube, EsdReport.fp,
                       metric, cpath, sevbuf);
    fflush(EsdReport.fp);
}

/*
    Append row of (metric,cpath) severity values to analysis report.
*/
void esd_report_sev_threads (int metricid, int cpathid, double* sevbuf)
{
    cube_metric* metric = epk_vector_at(EsdReport.metricv, metricid);
    cube_cnode*   cpath = epk_vector_at(EsdReport.cpathv, cpathid);

    cube_write_sev_threads(EsdReport.cube, EsdReport.fp,
                           metric, cpath, sevbuf);
    fflush(EsdReport.fp);
}


void esd_report_visits (int cpathid, elg_ui4 visits)
{
    cube_cnode*   cpath = epk_vector_at(EsdReport.cpathv, cpathid);
    cube_region* region = cube_cnode_get_callee(cpath);
    int           level = cube_cnode_get_level(cpath);
    printf("P%03u:[%u]visits=%u  %s\n", cpathid, level, visits,
                cube_region_get_name(region));
}


static unsigned esd_paths_reported = 0;

/*
    Append a callpath description to the callpath report.
*/
void esd_report_cpath (cube_cnode* cnode)
{
    unsigned i;
    cube_region* region = cube_cnode_get_callee(cnode);
    int level = cube_cnode_get_level(cnode);
    for (i=1; i<level; i++) fprintf(EsdReport.fp, " | ");
    if (level) fprintf(EsdReport.fp, " + ");
    fprintf(EsdReport.fp, "%s\n", cube_region_get_name(region));
    esd_paths_reported++;
    for (i=0; i<cube_cnode_num_children(cnode); i++) {
        esd_report_cpath(cube_cnode_get_child(cnode, i));
    }
}

/*
    Append a callpath description to the callpath report.
*/
void esd_report_ucpath (EsdUnifyData* udata, UnifyCpath* ucpath, unsigned level)
{
    unsigned i;
    elg_ui4 rid = ucpath->rid;
    unsigned children = ucpath->children ? epk_vector_size(ucpath->children) : 0;
    UnifyRegion* uregion = epk_vector_at(udata->regions, rid);
    char* regnname = epk_vector_at(udata->strings, uregion->nameid);
    elg_cntl_msg("%d: cpath[%d]: rid=%d ppid=%d children=%d: %s", level,
            esd_paths_reported, ucpath->rid, ucpath->ppid, children, regnname);

    for (i=1; i<level; i++) fprintf(EsdReport.fp, " | ");
    if (level) fprintf(EsdReport.fp, " + ");
    fprintf(EsdReport.fp, "%s\n", regnname);

    esd_paths_reported++;
    level++;
    for (i=0; i<children; i++) {
        esd_report_ucpath(udata, epk_vector_at(ucpath->children, i), level);
    }
}

/*
    Produce a report of unified callpaths.
*/
void esd_report_paths (EsdUnifyData* udata, int unknowns)
{
    static char* pathfile = "epik.path";
    char* filename = malloc(256);

    sprintf(filename, "%s/%s", epk_archive_get_name(), pathfile);

    if (EsdReport.fp != NULL)
        elg_warning("Analysis report %s still open!", EsdReport.filename);

    EsdReport.fp = fopen(filename, "w");
    if (EsdReport.fp == NULL)
        elg_warning("Error opening paths report %s: %s", filename, strerror(errno));
    else
        elg_cntl_msg("Generating paths report %s ...", filename);

    EsdReport.filename = filename;

    elg_cntl_msg("unified cpaths(%d) unknowns=%d", epk_vector_size(udata->cpaths), unknowns);
    esd_paths_reported = (unknowns) ? 0 : 1; /* skip unknown path if missing */

    /* report each tree of paths until all are reported */
    while (esd_paths_reported < epk_vector_size(udata->cpaths))
        esd_report_ucpath(udata, epk_vector_at(udata->cpaths, esd_paths_reported), 0);

#if 0
    if (EsdReport.cube) {
        /* report each tree of paths until all are reported */
        while (esd_paths_reported < epk_vector_size(EsdReport.cpathv))
            esd_report_cpath(epk_vector_at(EsdReport.cpathv, esd_paths_reported));
    }
#endif

    if (fclose(EsdReport.fp))
        elg_warning("Error closing paths report %s: %s", EsdReport.filename, strerror(errno));
    else
        elg_cntl_msg("Paths report %s generation complete.", EsdReport.filename);
}

/*
    Finish and close the analysis report and free associated datastructures.
*/
void esd_report_close ()
{
    if (!EsdReport.cube)
        elg_error_msg("No report defined to close!");

    /* generate report epilogue */
    cube_write_finish(EsdReport.cube, EsdReport.fp);

    if (fclose(EsdReport.fp))
        elg_warning("Error closing analysis report %s: %s", EsdReport.filename, strerror(errno));
    else
        elg_cntl_msg("Analysis report %s generation complete.", EsdReport.filename);

    EsdReport.fp = NULL; /* not always reset on successful fclose */

    cube_free(EsdReport.cube);
}
