/*
 * Copyright (c) 2006-2009 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.
 *
 * Authors: Ali Saidi
 */

#ifndef __BASE__CP_ANNOTATE_HH__
#define __BASE__CP_ANNOTATE_HH__

#include <string>
#include <list>
#include <vector>
#include <map>

#include "base/hashmap.hh"
#include "base/loader/symtab.hh"
#include "base/trace.hh"
#include "base/types.hh"
#include "config/cp_annotate.hh"
#include "config/the_isa.hh"
#include "sim/serialize.hh"
#include "sim/system.hh"

#if CP_ANNOTATE
#include "params/CPA.hh"
#endif

class System;
class ThreadContext;


#if !CP_ANNOTATE
class CPA
{
  public:
    enum flags {
        FL_NONE     = 0x00,
        FL_HW       = 0x01,
        FL_BAD      = 0x02,
        FL_QOPP     = 0x04,
        FL_WAIT     = 0x08,
        FL_LINK     = 0x10,
        FL_RESET    = 0x20
    };

    static CPA *cpa()                                        { return NULL; }
    static bool available()                                 { return false; }
    bool enabled()                                          { return false; }
    void swSmBegin(ThreadContext *tc)                             { return; }
    void swSmEnd(ThreadContext *tc)                               { return; }
    void swExplictBegin(ThreadContext *tc)                        { return; }
    void swAutoBegin(ThreadContext *tc, Addr next_pc)             { return; }
    void swEnd(ThreadContext *tc)                                 { return; }
    void swQ(ThreadContext *tc)                                   { return; }
    void swDq(ThreadContext *tc)                                  { return; }
    void swPq(ThreadContext *tc)                                  { return; }
    void swRq(ThreadContext *tc)                                  { return; }
    void swWf(ThreadContext *tc)                                  { return; }
    void swWe(ThreadContext *tc)                                  { return; }
    void swSq(ThreadContext *tc)                                  { return; }
    void swAq(ThreadContext *tc)                                  { return; }
    void swLink(ThreadContext *tc)                                { return; }
    void swIdentify(ThreadContext *tc)                            { return; }
    uint64_t swGetId(ThreadContext *tc)                         { return 0; }
    void swSyscallLink(ThreadContext *tc)                         { return; }
    void hwBegin(flags f, System *sys, uint64_t frame, std::string sm, 
                 std::string st)                                  { return; }
    void hwQ(flags f, System *sys, uint64_t frame, std::string sm, 
             std::string q, uint64_t qid, System *q_sys = NULL, 
             int32_t count = 1)                                   { return; }
    void hwDq(flags f, System *sys, uint64_t frame, std::string sm, 
              std::string q, uint64_t qid, System *q_sys = NULL, 
              int32_t count = 1)                                  { return; }
    void hwPq(flags f, System *sys, uint64_t frame, std::string sm, 
              std::string q, uint64_t qid, System *q_sys = NULL, 
              int32_t count = 1)                                  { return; }
    void hwRq(flags f, System *sys, uint64_t frame, std::string sm, 
              std::string q, uint64_t qid, System *q_sys = NULL, 
              int32_t count = 1)                                  { return; }
    void hwWf(flags f, System *sys, uint64_t frame, std::string sm, 
              std::string q, uint64_t qid, System *q_sys = NULL, 
              int32_t count = 1)                                  { return; }
    void hwWe(flags f, System *sys, uint64_t frame, std::string sm, 
              std::string q, uint64_t qid, System *q_sys = NULL, 
              int32_t count = 1)                                  { return; }
};
#else
class CPA : SimObject
{
  public:
    typedef CPAParams Params;
    
    /** The known operations that are written to the annotation output file. */
    enum ops {
        OP_BEGIN           = 0x01,
        OP_WAIT_EMPTY      = 0x02,
        OP_WAIT_FULL       = 0x03,
        OP_QUEUE           = 0x04,
        OP_DEQUEUE         = 0x05,
        OP_SIZE_QUEUE      = 0x08,
        OP_PEEK            = 0x09,
        OP_LINK            = 0x0A,
        OP_IDENT           = 0x0B,
        OP_RESERVE         = 0x0C
    };

    /** Flags for the various options.*/
    enum flags {
        /* no flags */
        FL_NONE     = 0x00,
        /* operation was done on hardware */
        FL_HW       = 0x01,
        /* operation should cause a warning when encountered */
        FL_BAD      = 0x02,
        /* Queue like a stack, not a queue */
        FL_QOPP     = 0x04,
        /* Mark HW state as waiting for some non-resource constraint
         * (e.g. wait because SM only starts after 10 items are queued) */ 
        FL_WAIT     = 0x08,
        /* operation is linking to another state machine */
        FL_LINK     = 0x10,
        /* queue should be completely cleared/reset before executing this
         * operation */
        FL_RESET    = 0x20
    };



  protected:
    const Params *
        params() const
        {
            return dynamic_cast<const Params *>(_params);
        }

    /* struct that is written to the annotation output file */
    struct AnnotateData : public RefCounted {

        Tick time;
        uint32_t data;
        uint32_t orig_data;
        uint16_t sm;
        uint16_t stq;
        uint8_t  op;
        uint8_t  flag;
        uint8_t  cpu;
        bool dump;

        void serialize(std::ostream &os);
        void unserialize(Checkpoint *cp, const std::string &section);

    };

    typedef RefCountingPtr<AnnotateData> AnnDataPtr;

    /* header for the annotation file */
    struct AnnotateHeader {
        uint64_t version;
        uint64_t num_recs;
        uint64_t key_off;
        uint64_t idx_off;
        uint32_t key_len;
        uint32_t idx_len;
    };

    AnnotateHeader ah;

    std::vector<uint64_t> annotateIdx;

    // number of state machines encountered in the simulation 
    int numSm;
    // number of states encountered in the simulation 
    int numSmt;
    // number of states/queues for a given state machine/system respectively
    std::vector<int> numSt, numQ;
    // number of systems in the simulation
    int numSys;
    // number of queues in the state machine
    int numQs;
    // maximum connection id assigned so far
    uint64_t conId;

    // Convert state strings into state ids
    typedef m5::hash_map<std::string, int> SCache;
    typedef std::vector<SCache> StCache;

    // Convert sm and queue name,id into queue id
    typedef std::pair<std::string, uint64_t> Id;
    typedef m5::hash_map<Id, int> IdHCache;
    typedef std::vector<IdHCache> IdCache;

    // Hold mapping of sm and queues to output python
    typedef std::vector<std::pair<int, Id> > IdMap;

    // System pointer to name,id
    typedef std::map<System*, std::pair<std::string, int> > NameCache;

    // array of systems each of which is a stack of running sm
    typedef std::pair<int, uint64_t> StackId;
    typedef std::map<StackId, std::vector<int> > SmStack;

    // map of each context and if it's currently in explict state mode
    // states are not automatically updated until it leaves
    typedef std::map<StackId, bool> SwExpl;

    typedef std::map<int,int> IMap;
    // List of annotate records have not been written/completed yet
    typedef std::list<AnnDataPtr> AnnotateList;

    // Maintain link state information
    typedef std::map<int, int> LinkMap;

    // SC Links
    typedef m5::hash_map<Id, AnnDataPtr> ScHCache;
    typedef std::vector<ScHCache> ScCache;


    AnnotateList data;

    // vector indexed by queueid to find current number of elements and bytes
    std::vector<int> qSize;
    std::vector<int32_t> qBytes;


    // Turn state machine string into state machine id (small int)
    // Used for outputting key to convert id back into string
    SCache smtCache;
    // Turn state machine id, state name into state id (small int)
    StCache stCache;
    // turn system, queue, and queue identify into qid (small int)
    // turn system, state, and context into state machine id (small int)
    IdCache qCache, smCache;
    //Link state machines accross system calls
    ScCache scLinks;
    // System pointer to name,id
    NameCache nameCache;
    // Stack of state machines currently nested (should unwind correctly)
    SmStack smStack;
    // Map of currently outstanding links 
    LinkMap lnMap;
    // If the state machine is currently exculding automatic changes
    SwExpl swExpl;
    // Last state that a given state machine was in
    IMap lastState;
    // Hold mapping of sm and queues to output python
    IdMap smMap, qMap;
    // Items still in queue, used for sanity checking 
    std::vector<AnnotateList> qData;

    void doDq(System *sys, int flags, int cpu, int sm, std::string q, int qi,
            int count);
    void doQ(System *sys, int flags, int cpu, int sm, std::string q, int qi,
            int count);

    void doSwSmEnd(System *sys, int cpuid, std::string sm, uint64_t frame);

    // Turn a system id, state machine string, state machine id into a small int
    // for annotation output
    int 
    getSm(int sysi, std::string si, uint64_t id)
    {
        int smi;
        Id smid = Id(si, id);

        smi = smCache[sysi-1][smid];
        if (smi == 0) {
            smCache[sysi-1][smid] = smi = ++numSm;
            assert(smi < 65535);
            smMap.push_back(std::make_pair<int, Id>(sysi, smid));
        }
        return smi;
    }

    // Turn a state machine string, state string into a small int
    // for annotation output
    int 
    getSt(std::string sm, std::string s)
    {
        int sti, smi;

        smi = smtCache[sm];
        if (smi == 0)
           smi = smtCache[sm] = ++numSmt;

        while (stCache.size() < smi) {
            //stCache.resize(sm);
            stCache.push_back(SCache());
            numSt.push_back(0);
        }
        //assert(stCache.size() == sm);
        //assert(numSt.size() == sm);
        sti = stCache[smi-1][s];
        if (sti == 0)
            stCache[smi-1][s] = sti = ++numSt[smi-1];
        return sti;
    }

    // Turn state machine pointer into a smal int for annotation output
    int 
    getSys(System *s)
    {
        NameCache::iterator i = nameCache.find(s);
        if (i == nameCache.end()) {
            nameCache[s] = std::make_pair<std::string,int>(s->name(), ++numSys);
            i = nameCache.find(s);
            // might need to put smstackid into map here, but perhaps not
            //smStack.push_back(std::vector<int>());
            //swExpl.push_back(false);
            numQ.push_back(0);
            qCache.push_back(IdHCache());
            smCache.push_back(IdHCache());
            scLinks.push_back(ScHCache());
        }
        return i->second.second;
    }

    // Turn queue name, and queue context into small int for 
    // annotation output
    int 
    getQ(int sys, std::string q, uint64_t id)
    {
        int qi;
        Id qid = Id(q, id);

        qi = qCache[sys-1][qid];
        if (qi == 0) {
            qi = qCache[sys-1][qid] = ++numQs;
            assert(qi < 65535);
            qSize.push_back(0);
            qBytes.push_back(0);
            qData.push_back(AnnotateList());
            numQ[sys-1]++;
            qMap.push_back(std::make_pair<int, Id>(sys, qid));
        }
        return qi;
    }

    void swBegin(System *sys, int cpuid, std::string st, uint64_t frame, 
            bool expl = false, int flags = FL_NONE);

    AnnDataPtr add(int t, int f, int c, int sm, int stq, int32_t data=0);

    std::ostream *osbin;

    bool _enabled;

    /** Only allow one CPA object in a system. It doesn't make sense to have 
     * more that one per simulation because if a part of the system was
     * important it would have annotations and queues, and with more than one
     * object none of the sanity checking for queues will work. */
    static bool exists;
    static CPA *_cpa;


    std::map<std::string, SymbolTable*> userApp;

  public:
    static CPA *cpa() { return _cpa; }
    void swSmBegin(ThreadContext *tc);
    void swSmEnd(ThreadContext *tc);
    void swExplictBegin(ThreadContext *tc);
    void swAutoBegin(ThreadContext *tc, Addr next_pc);
    void swEnd(ThreadContext *tc);
    void swQ(ThreadContext *tc);
    void swDq(ThreadContext *tc);
    void swPq(ThreadContext *tc);
    void swRq(ThreadContext *tc);
    void swWf(ThreadContext *tc);
    void swWe(ThreadContext *tc);
    void swSq(ThreadContext *tc);
    void swAq(ThreadContext *tc);
    void swLink(ThreadContext *tc);
    void swIdentify(ThreadContext *tc);
    uint64_t swGetId(ThreadContext *tc);
    void swSyscallLink(ThreadContext *tc);

    inline void hwBegin(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string st)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int smi = getSm(sysi, sm, frame);
        add(OP_BEGIN, FL_HW | f, 0, smi, getSt(sm, st));
        if (f & FL_BAD)
            warn("BAD state encountered: at cycle %d: %s\n", curTick(), st);
    }

    inline void hwQ(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        DPRINTFS(AnnotateQ, sys, 
                "hwQ: %s[%#x] cur size %d %d bytes: %d adding: %d\n",
                q, qid, qSize[qi-1], qData[qi-1].size(), qBytes[qi-1], count);
        doQ(sys, FL_HW | f, 0, getSm(sysi, sm, frame), q, qi, count);

    }

    inline void hwDq(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        DPRINTFS(AnnotateQ, sys, 
                "hwDQ: %s[%#x] cur size %d %d bytes: %d removing: %d\n",
                q, qid, qSize[qi-1], qData[qi-1].size(), qBytes[qi-1], count);
        doDq(sys, FL_HW | f, 0, getSm(sysi,sm, frame), q, qi, count);
    }

    inline void hwPq(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        DPRINTFS(AnnotateQ, sys, 
                "hwPQ: %s[%#x] cur size %d %d bytes: %d peeking: %d\n",
                q, qid, qSize[qi-1], qData[qi-1].size(), qBytes[qi-1], count);
        add(OP_PEEK, FL_HW | f, 0, getSm(sysi, sm, frame), qi, count);
    }

    inline void hwRq(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        DPRINTFS(AnnotateQ, sys, 
                "hwRQ: %s[%#x] cur size %d %d bytes: %d reserving: %d\n",
                q, qid, qSize[qi-1], qData[qi-1].size(), qBytes[qi-1], count);
        add(OP_RESERVE, FL_HW | f, 0, getSm(sysi, sm, frame), qi, count);
    }

    inline void hwWf(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1)
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        add(OP_WAIT_FULL, FL_HW | f, 0, getSm(sysi, sm, frame), qi, count);
    }

    inline void hwWe(flags f, System *sys, uint64_t frame, std::string sm, 
            std::string q, uint64_t qid, System *q_sys = NULL, int32_t count = 1) 
    {
        if (!enabled())
            return;

        int sysi = getSys(sys);
        int qi = getQ(q_sys ?  getSys(q_sys) : sysi, q, qid);
        add(OP_WAIT_EMPTY, FL_HW | f, 0, getSm(sysi, sm, frame), qi, count);
    }

  public:
    CPA(Params *p);
    void startup();

    // This code is ISA specific and will need to be changed
    // if the annotation code is used for something other than Alpha
    inline uint64_t getFrame(ThreadContext *tc)
        { return (tc->readMiscRegNoEffect(TheISA::IPR_PALtemp23) & 
                ~ULL(0x3FFF)); }

    static bool available()  { return true; }

    bool 
    enabled() 
    {   
        if (!this)
            return false;
        return _enabled;
    }
        
    void dump(bool all);
    void dumpKey();

    void serialize(std::ostream &os);
    void unserialize(Checkpoint *cp, const std::string &section);

};
#endif // !CP_ANNOTATE

#endif //__BASE__CP_ANNOTATE_HH__