/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Non-blocking event logger intended for safe communication between processes via shared memory

#ifndef ANDROID_MEDIA_NBLOG_H
#define ANDROID_MEDIA_NBLOG_H

#include <deque>
#include <map>
#include <set>
#include <vector>

#include <audio_utils/fifo.h>
#include <binder/IMemory.h>
#include <media/nblog/PerformanceAnalysis.h>
#include <media/nblog/ReportPerformance.h>
#include <utils/Mutex.h>
#include <utils/threads.h>

namespace android {

class String8;

class NBLog {

public:

    using log_hash_t = ReportPerformance::log_hash_t;

    // FIXME Everything needed for client (writer API and registration) should be isolated
    //       from the rest of the implementation.
    class Writer;
    class Reader;

    enum Event : uint8_t {
        EVENT_RESERVED,
        EVENT_STRING,               // ASCII string, not NUL-terminated
                                    // TODO: make timestamp optional
        EVENT_TIMESTAMP,            // clock_gettime(CLOCK_MONOTONIC)
        EVENT_INTEGER,              // integer value entry
        EVENT_FLOAT,                // floating point value entry
        EVENT_PID,                  // process ID and process name
        EVENT_AUTHOR,               // author index (present in merged logs) tracks entry's
                                    // original log
        EVENT_START_FMT,            // logFormat start event: entry includes format string,
                                    // following entries contain format arguments
        EVENT_HASH,                 // unique HASH of log origin, originates from hash of file name
                                    // and line number
        EVENT_HISTOGRAM_ENTRY_TS,   // single datum for timestamp histogram
        EVENT_AUDIO_STATE,          // audio on/off event: logged on FastMixer::onStateChange call
        EVENT_END_FMT,              // end of logFormat argument list

        EVENT_UPPER_BOUND,          // to check for invalid events
    };

private:

    // ---------------------------------------------------------------------------
    // API for handling format entry operations

    // a formatted entry has the following structure:
    //    * START_FMT entry, containing the format string
    //    * TIMESTAMP entry
    //    * HASH entry
    //    * author entry of the thread that generated it (optional, present in merged log)
    //    * format arg1
    //    * format arg2
    //    * ...
    //    * END_FMT entry

    // entry representation in memory
    struct entry {
        const uint8_t type;
        const uint8_t length;
        const uint8_t data[0];
    };

    // entry tail representation (after data)
    struct ending {
        uint8_t length;
        uint8_t next[0];
    };

    // entry iterator
    class EntryIterator {
    public:
        EntryIterator();
        explicit EntryIterator(const uint8_t *entry);
        EntryIterator(const EntryIterator &other);

        // dereference underlying entry
        const entry&    operator*() const;
        const entry*    operator->() const;
        // advance to next entry
        EntryIterator&       operator++(); // ++i
        // back to previous entry
        EntryIterator&       operator--(); // --i
        EntryIterator        next() const;
        EntryIterator        prev() const;
        bool            operator!=(const EntryIterator &other) const;
        int             operator-(const EntryIterator &other) const;

        bool            hasConsistentLength() const;
        void            copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
        void            copyData(uint8_t *dst) const;

        template<typename T>
        inline const T& payload() {
            return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
        }

        inline operator const uint8_t*() const {
            return ptr;
        }

    private:
        const uint8_t  *ptr;
    };

    class AbstractEntry {
    public:

        // Entry starting in the given pointer
        explicit AbstractEntry(const uint8_t *entry);
        virtual ~AbstractEntry() {}

        // build concrete entry of appropriate class from pointer
        static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);

        // get format entry timestamp
        virtual int64_t      timestamp() const = 0;

        // get format entry's unique id
        virtual log_hash_t   hash() const = 0;

        // entry's author index (-1 if none present)
        // a Merger has a vector of Readers, author simply points to the index of the
        // Reader that originated the entry
        // TODO consider changing to uint32_t
        virtual int          author() const = 0;

        // copy entry, adding author before timestamp, returns iterator to end of entry
        virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
                                                int author) const = 0;

    protected:
        // copies ordinary entry from src to dst, and returns length of entry
        // size_t      copyEntry(audio_utils_fifo_writer *dst, const iterator &it);
        const uint8_t  *mEntry;
    };

    class FormatEntry : public AbstractEntry {
    public:
        // explicit FormatEntry(const EntryIterator &it);
        explicit FormatEntry(const uint8_t *ptr) : AbstractEntry(ptr) {}
        virtual ~FormatEntry() {}

        EntryIterator begin() const;

        // Entry's format string
        const   char* formatString() const;

        // Enrty's format string length
        size_t      formatStringLength() const;

        // Format arguments (excluding format string, timestamp and author)
        EntryIterator    args() const;

        // get format entry timestamp
        virtual int64_t     timestamp() const override;

        // get format entry's unique id
        virtual log_hash_t  hash() const override;

        // entry's author index (-1 if none present)
        // a Merger has a vector of Readers, author simply points to the index of the
        // Reader that originated the entry
        virtual int         author() const override;

        // copy entry, adding author before timestamp, returns size of original entry
        virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
                                                int author) const override;

    };

    class HistogramEntry : public AbstractEntry {
    public:
        explicit HistogramEntry(const uint8_t *ptr) : AbstractEntry(ptr) {
        }
        virtual ~HistogramEntry() {}

        virtual int64_t     timestamp() const override;

        virtual log_hash_t  hash() const override;

        virtual int         author() const override;

        virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
                                                int author) const override;

    };

    // ---------------------------------------------------------------------------

    // representation of a single log entry in private memory
    struct Entry {
        Entry(Event event, const void *data, size_t length)
            : mEvent(event), mLength(length), mData(data) { }
        /*virtual*/ ~Entry() { }

        // used during writing to format Entry information as follows:
        // [type][length][data ... ][length]
        int     copyEntryDataAt(size_t offset) const;

    private:
        friend class Writer;
        Event       mEvent;     // event type
        uint8_t     mLength;    // length of additional data, 0 <= mLength <= kMaxLength
        const void *mData;      // event type-specific data
        static const size_t kMaxLength = 255;
    public:
        // mEvent, mLength, mData[...], duplicate mLength
        static const size_t kOverhead = sizeof(entry) + sizeof(ending);
        // endind length of previous entry
        static const ssize_t kPreviousLengthOffset = - sizeof(ending) +
            offsetof(ending, length);
    };

    struct HistTsEntry {
        log_hash_t hash;
        int64_t ts;
    }; //TODO __attribute__((packed));

    struct HistTsEntryWithAuthor {
        log_hash_t hash;
        int64_t ts;
        int author;
    }; //TODO __attribute__((packed));

    struct HistIntEntry {
        log_hash_t hash;
        int value;
    }; //TODO __attribute__((packed));

    // representation of a single log entry in shared memory
    //  byte[0]             mEvent
    //  byte[1]             mLength
    //  byte[2]             mData[0]
    //  ...
    //  byte[2+i]           mData[i]
    //  ...
    //  byte[2+mLength-1]   mData[mLength-1]
    //  byte[2+mLength]     duplicate copy of mLength to permit reverse scan
    //  byte[3+mLength]     start of next log entry

    static void    appendInt(String8 *body, const void *data);
    static void    appendFloat(String8 *body, const void *data);
    static void    appendPID(String8 *body, const void *data, size_t length);
    static void    appendTimestamp(String8 *body, const void *data);
    static size_t  fmtEntryLength(const uint8_t *data);
    static String8 bufferDump(const uint8_t *buffer, size_t size);
    static String8 bufferDump(const EntryIterator &it);
public:

    // Located in shared memory, must be POD.
    // Exactly one process must explicitly call the constructor or use placement new.
    // Since this is a POD, the destructor is empty and unnecessary to call it explicitly.
    struct Shared {
        Shared() /* mRear initialized via default constructor */ { }
        /*virtual*/ ~Shared() { }

        audio_utils_fifo_index  mRear;  // index one byte past the end of most recent Entry
        char    mBuffer[0];             // circular buffer for entries
    };

public:

    // ---------------------------------------------------------------------------

    // FIXME Timeline was intended to wrap Writer and Reader, but isn't actually used yet.
    // For now it is just a namespace for sharedSize().
    class Timeline : public RefBase {
    public:
#if 0
        Timeline(size_t size, void *shared = NULL);
        virtual ~Timeline();
#endif

        // Input parameter 'size' is the desired size of the timeline in byte units.
        // Returns the size rounded up to a power-of-2, plus the constant size overhead for indices.
        static size_t sharedSize(size_t size);

#if 0
    private:
        friend class    Writer;
        friend class    Reader;

        const size_t    mSize;      // circular buffer size in bytes, must be a power of 2
        bool            mOwn;       // whether I own the memory at mShared
        Shared* const   mShared;    // pointer to shared memory
#endif
    };

    // ---------------------------------------------------------------------------

    // Writer is thread-safe with respect to Reader, but not with respect to multiple threads
    // calling Writer methods.  If you need multi-thread safety for writing, use LockedWriter.
    class Writer : public RefBase {
    public:
        Writer();                   // dummy nop implementation without shared memory

        // Input parameter 'size' is the desired size of the timeline in byte units.
        // The size of the shared memory must be at least Timeline::sharedSize(size).
        Writer(void *shared, size_t size);
        Writer(const sp<IMemory>& iMemory, size_t size);

        virtual ~Writer();

        // FIXME needs comments, and some should be private
        virtual void    log(const char *string);
        virtual void    logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
        virtual void    logvf(const char *fmt, va_list ap);
        virtual void    logTimestamp();
        virtual void    logTimestamp(const int64_t ts);
        virtual void    logInteger(const int x);
        virtual void    logFloat(const float x);
        virtual void    logPID();
        virtual void    logFormat(const char *fmt, log_hash_t hash, ...);
        virtual void    logVFormat(const char *fmt, log_hash_t hash, va_list ap);
        virtual void    logStart(const char *fmt);
        virtual void    logEnd();
        virtual void    logHash(log_hash_t hash);
        virtual void    logEventHistTs(Event event, log_hash_t hash);

        virtual bool    isEnabled() const;

        // return value for all of these is the previous isEnabled()
        virtual bool    setEnabled(bool enabled);   // but won't enable if no shared memory
        bool    enable()    { return setEnabled(true); }
        bool    disable()   { return setEnabled(false); }

        sp<IMemory>     getIMemory() const  { return mIMemory; }

    private:
        // 0 <= length <= kMaxLength
        // writes a single Entry to the FIFO
        void    log(Event event, const void *data, size_t length);
        // checks validity of an event before calling log above this one
        void    log(const Entry *entry, bool trusted = false);

        Shared* const   mShared;    // raw pointer to shared memory
        sp<IMemory>     mIMemory;   // ref-counted version, initialized in constructor
                                    // and then const
        audio_utils_fifo * const mFifo;                 // FIFO itself, non-NULL
                                                        // unless constructor fails
        audio_utils_fifo_writer * const mFifoWriter;    // used to write to FIFO, non-NULL
                                                        // unless dummy constructor used
        bool            mEnabled;   // whether to actually log

        // cached pid and process name to use in %p format specifier
        // total tag length is mPidTagSize and process name is not zero terminated
        char   *mPidTag;
        size_t  mPidTagSize;
    };

    // ---------------------------------------------------------------------------

    // Similar to Writer, but safe for multiple threads to call concurrently
    class LockedWriter : public Writer {
    public:
        LockedWriter();
        LockedWriter(void *shared, size_t size);

        virtual void    log(const char *string);
        virtual void    logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
        virtual void    logvf(const char *fmt, va_list ap);
        virtual void    logTimestamp();
        virtual void    logTimestamp(const int64_t ts);
        virtual void    logInteger(const int x);
        virtual void    logFloat(const float x);
        virtual void    logPID();
        virtual void    logStart(const char *fmt);
        virtual void    logEnd();
        virtual void    logHash(log_hash_t hash);

        virtual bool    isEnabled() const;
        virtual bool    setEnabled(bool enabled);

    private:
        mutable Mutex   mLock;
    };

    // ---------------------------------------------------------------------------

    class Reader : public RefBase {
    public:
        // A snapshot of a readers buffer
        // This is raw data. No analysis has been done on it
        class Snapshot {
        public:
            Snapshot() : mData(NULL), mLost(0) {}

            Snapshot(size_t bufferSize) : mData(new uint8_t[bufferSize]) {}

            ~Snapshot() { delete[] mData; }

            // copy of the buffer
            uint8_t *data() const { return mData; }

            // amount of data lost (given by audio_utils_fifo_reader)
            size_t   lost() const { return mLost; }

            // iterator to beginning of readable segment of snapshot
            // data between begin and end has valid entries
            EntryIterator begin() { return mBegin; }

            // iterator to end of readable segment of snapshot
            EntryIterator end() { return mEnd; }

        private:
            friend class MergeReader;
            friend class Reader;
            uint8_t              *mData;
            size_t                mLost;
            EntryIterator mBegin;
            EntryIterator mEnd;
        };

        // Input parameter 'size' is the desired size of the timeline in byte units.
        // The size of the shared memory must be at least Timeline::sharedSize(size).
        Reader(const void *shared, size_t size);
        Reader(const sp<IMemory>& iMemory, size_t size);

        virtual ~Reader();

        // get snapshot of readers fifo buffer, effectively consuming the buffer
        std::unique_ptr<Snapshot> getSnapshot();

        bool     isIMemory(const sp<IMemory>& iMemory) const;

    protected:
        // print a summary of the performance to the console
        void    dumpLine(const String8& timestamp, String8& body);
        EntryIterator   handleFormat(const FormatEntry &fmtEntry,
                                     String8 *timestamp,
                                     String8 *body);
        int mFd;                // file descriptor
        int mIndent;            // indentation level
        int mLost;              // bytes of data lost before buffer was read

    private:
        static const std::set<Event> startingTypes;
        static const std::set<Event> endingTypes;

        // declared as const because audio_utils_fifo() constructor
        sp<IMemory> mIMemory;       // ref-counted version, assigned only in constructor

        /*const*/ Shared* const mShared;    // raw pointer to shared memory, actually const but not
        audio_utils_fifo * const mFifo;                 // FIFO itself,
        // non-NULL unless constructor fails
        audio_utils_fifo_reader * const mFifoReader;    // used to read from FIFO,
        // non-NULL unless constructor fails

        // Searches for the last entry of type <type> in the range [front, back)
        // back has to be entry-aligned. Returns nullptr if none enconuntered.
        static const uint8_t *findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
                                                   const std::set<Event> &types);

        // dummy method for handling absent author entry
        virtual void handleAuthor(const AbstractEntry& /*fmtEntry*/, String8* /*body*/) {}
    };

    // Wrapper for a reader with a name. Contains a pointer to the reader and a pointer to the name
    class NamedReader {
    public:
        NamedReader() { mName[0] = '\0'; } // for Vector
        NamedReader(const sp<NBLog::Reader>& reader, const char *name) :
            mReader(reader)
            { strlcpy(mName, name, sizeof(mName)); }
        ~NamedReader() { }
        const sp<NBLog::Reader>&  reader() const { return mReader; }
        const char*               name() const { return mName; }

    private:
        sp<NBLog::Reader>   mReader;
        static const size_t kMaxName = 32;
        char                mName[kMaxName];
    };

    // ---------------------------------------------------------------------------

    // This class is used to read data from each thread's individual FIFO in shared memory
    // and write it to a single FIFO in local memory.
    class Merger : public RefBase {
    public:
        Merger(const void *shared, size_t size);

        virtual ~Merger() {}

        void addReader(const NamedReader &reader);
        // TODO add removeReader
        void merge();

        // FIXME This is returning a reference to a shared variable that needs a lock
        const std::vector<NamedReader>& getNamedReaders() const;

    private:
        // vector of the readers the merger is supposed to merge from.
        // every reader reads from a writer's buffer
        // FIXME Needs to be protected by a lock
        std::vector<NamedReader> mNamedReaders;

        Shared * const mShared; // raw pointer to shared memory
        std::unique_ptr<audio_utils_fifo> mFifo; // FIFO itself
        std::unique_ptr<audio_utils_fifo_writer> mFifoWriter; // used to write to FIFO
    };

    // This class has a pointer to the FIFO in local memory which stores the merged
    // data collected by NBLog::Merger from all NamedReaders. It is used to process
    // this data and write the result to PerformanceAnalysis.
    class MergeReader : public Reader {
    public:
        MergeReader(const void *shared, size_t size, Merger &merger);

        void dump(int fd, int indent = 0);
        // process a particular snapshot of the reader
        void getAndProcessSnapshot(Snapshot & snap);
        // call getSnapshot of the content of the reader's buffer and process the data
        void getAndProcessSnapshot();

    private:
        // FIXME Needs to be protected by a lock,
        //       because even though our use of it is read-only there may be asynchronous updates
        const std::vector<NamedReader>& mNamedReaders;

        // analyzes, compresses and stores the merged data
        // contains a separate instance for every author (thread), and for every source file
        // location within each author
        ReportPerformance::PerformanceAnalysisMap mThreadPerformanceAnalysis;

        // handle author entry by looking up the author's name and appending it to the body
        // returns number of bytes read from fmtEntry
        void handleAuthor(const AbstractEntry &fmtEntry, String8 *body);
    };

    // MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
    // when triggered, it awakes for a lapse of time, during which it periodically merges; if
    // retriggered, the timeout is reset.
    // The thread is triggered on AudioFlinger binder activity.
    class MergeThread : public Thread {
    public:
        MergeThread(Merger &merger, MergeReader &mergeReader);
        virtual ~MergeThread() override;

        // Reset timeout and activate thread to merge periodically if it's idle
        void wakeup();

        // Set timeout period until the merging thread goes idle again
        void setTimeoutUs(int time);

    private:
        virtual bool threadLoop() override;

        // the merger who actually does the work of merging the logs
        Merger&     mMerger;

        // the mergereader used to process data merged by mMerger
        MergeReader& mMergeReader;

        // mutex for the condition variable
        Mutex       mMutex;

        // condition variable to activate merging on timeout >= 0
        Condition   mCond;

        // time left until the thread blocks again (in microseconds)
        int         mTimeoutUs;

        // merging period when the thread is awake
        static const int  kThreadSleepPeriodUs = 1000000 /*1s*/;

        // initial timeout value when triggered
        static const int  kThreadWakeupPeriodUs = 3000000 /*3s*/;
    };

};  // class NBLog

// TODO put somewhere else
static inline int64_t get_monotonic_ns() {
    timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
        return (uint64_t) ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
    }
    return 0; // should not happen.
}

}   // namespace android

#endif  // ANDROID_MEDIA_NBLOG_H
