/*
 * Copyright (c) 2001-2005 The Regents of The University of Michigan
 * 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 the copyright holders 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.
 */

#include <ctype.h>
#include <fstream>
#include <iostream>
#include <list>
#include <string>
#include <vector>

#include "base/misc.hh"
#include "base/trace.hh"
#include "base/str.hh"

using namespace std;

namespace Trace {
const string DefaultName("global");
FlagVec flags(NumFlags, false);

//
// This variable holds the output stream for debug information.  Other
// than setting up/redirecting this stream, do *NOT* reference this
// directly; use DebugOut() (see below) to access this stream for
// output.
//
ostream *dprintf_stream = &cerr;

ObjectMatch ignore;

Log theLog;

Log::Log()
{
    size = 0;
    buffer = NULL;
}


void
Log::init(int _size)
{
    if (buffer != NULL) {
        fatal("Trace::Log::init called twice!");
    }

    size = _size;

    buffer = new Record *[size];

    for (int i = 0; i < size; ++i) {
        buffer[i] = NULL;
    }

    nextRecPtr = &buffer[0];
    wrapRecPtr = &buffer[size];
}


Log::~Log()
{
    for (int i = 0; i < size; ++i) {
        delete buffer[i];
    }

    delete [] buffer;
}


void
Log::append(Record *rec)
{
    // dump record to output stream if there's one open
    if (dprintf_stream != NULL) {
        rec->dump(*dprintf_stream);
    } else {
        rec->dump(cout);
    }

    // no buffering: justget rid of it now
    if (buffer == NULL) {
        delete rec;
        return;
    }

    Record *oldRec = *nextRecPtr;

    if (oldRec != NULL) {
        // log has wrapped: overwrite
        delete oldRec;
    }

    *nextRecPtr = rec;

    if (++nextRecPtr == wrapRecPtr) {
        nextRecPtr = &buffer[0];
    }
}


void
Log::dump(ostream &os)
{
    if (buffer == NULL) {
        return;
    }

    Record **bufPtr = nextRecPtr;

    if (*bufPtr == NULL) {
        // next record slot is empty: log must not be full yet.
        // start dumping from beginning of buffer
        bufPtr = buffer;
    }

    do {
        Record *rec = *bufPtr;

        rec->dump(os);

        if (++bufPtr == wrapRecPtr) {
            bufPtr = &buffer[0];
        }
    } while (bufPtr != nextRecPtr);
}

PrintfRecord::~PrintfRecord()
{
    delete &args;
}

void
PrintfRecord::dump(ostream &os)
{
    string fmt = "";

    if (!name.empty()) {
        fmt = "%s: " + fmt;
        args.prepend(name);
    }

    if (cycle != (Tick)-1) {
        fmt = "%7d: " + fmt;
        args.prepend(cycle);
    }

    fmt += format;

    args.dump(os, fmt);
    os.flush();
}

DataRecord::DataRecord(Tick _cycle, const string &_name,
                       const void *_data, int _len)
    : Record(_cycle), name(_name), len(_len)
{
    data = new uint8_t[len];
    memcpy(data, _data, len);
}

DataRecord::~DataRecord()
{
    delete [] data;
}

void
DataRecord::dump(ostream &os)
{
    int c, i, j;

    for (i = 0; i < len; i += 16) {
        ccprintf(os, "%d: %s: %08x  ", cycle, name, i);
        c = len - i;
        if (c > 16) c = 16;

        for (j = 0; j < c; j++) {
            ccprintf(os, "%02x ", data[i + j] & 0xff);
            if ((j & 0xf) == 7 && j > 0)
                ccprintf(os, " ");
        }

        for (; j < 16; j++)
            ccprintf(os, "   ");
        ccprintf(os, "  ");

        for (j = 0; j < c; j++) {
            int ch = data[i + j] & 0x7f;
            ccprintf(os,
                     "%c", (char)(isprint(ch) ? ch : ' '));
        }

        ccprintf(os, "\n");

        if (c < 16)
            break;
    }
}
} // namespace Trace

//
// Returns the current output stream for debug information.  As a
// wrapper around Trace::dprintf_stream, this handles cases where debug
// information is generated in the process of parsing .ini options,
// before we process the option that sets up the debug output stream
// itself.
//
std::ostream &
DebugOut()
{
    return *Trace::dprintf_stream;
}

/////////////////////////////////////////////
//
// C-linkage functions for invoking from gdb
//
/////////////////////////////////////////////

//
// Dump trace buffer to specified file (cout if NULL)
//
extern "C"
void
dumpTrace(const char *filename)
{
    if (filename != NULL) {
        ofstream out(filename);
        Trace::theLog.dump(out);
        out.close();
    }
    else {
        Trace::theLog.dump(cout);
    }
}


//
// Turn on/off trace output to cerr.  Typically used when trace output
// is only going to circular buffer, but you want to see what's being
// sent there as you step through some code in gdb.  This uses the
// same facility as the "trace to file" feature, and will print error
// messages rather than clobbering an existing ostream pointer.
//
extern "C"
void
echoTrace(bool on)
{
    if (on) {
        if (Trace::dprintf_stream != NULL) {
            cerr << "Already echoing trace to a file... go do a 'tail -f'"
                 << " on that file instead." << endl;
        } else {
            Trace::dprintf_stream = &cerr;
        }
    } else {
        if (Trace::dprintf_stream != &cerr) {
            cerr << "Not echoing trace to cerr." << endl;
        } else {
            Trace::dprintf_stream = NULL;
        }
    }
}

extern "C"
void
printTraceFlags()
{
    using namespace Trace;
    for (int i = 0; i < numFlagStrings; ++i)
        if (flags[i])
            cprintf("%s\n", flagStrings[i]);
}

void
tweakTraceFlag(const char *string, bool value)
{
    using namespace Trace;
    std::string str(string);

    for (int i = 0; i < numFlagStrings; ++i) {
        if (str != flagStrings[i])
            continue;

        int idx = i;

        if (idx < NumFlags) {
            flags[idx] = value;
        } else {
            idx -= NumFlags;
            if (idx >= NumCompoundFlags) {
                ccprintf(cerr, "Invalid compound flag");
                return;
            }

            const Flags *flagVec = compoundFlags[idx];

            for (int j = 0; flagVec[j] != -1; ++j) {
                if (flagVec[j] >= NumFlags) {
                    ccprintf(cerr, "Invalid compound flag");
                    return;
                }
                flags[flagVec[j]] = value;
            }
        }

        cprintf("flag %s was %s\n", string, value ? "set" : "cleared");
        return;
    }

    cprintf("could not find flag %s\n", string);
}

extern "C"
void
setTraceFlag(const char *string)
{
    tweakTraceFlag(string, true);
}

extern "C"
void
clearTraceFlag(const char *string)
{
    tweakTraceFlag(string, false);
}