/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris.framework - C++ API
 *
 *  File: NewAPI_LogStream.hpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2022- Apple Inc. All rights reserved.
 *
 *  This document is the property of Apple Inc.
 *  It is considered confidential and proprietary.
 *
 *  This document may not be reproduced or transmitted in any form,
 *  in whole or in part, without the express written permission of
 *  Apple Inc.
 ********************************************************************* */

#ifndef NewAPI_LogStream_hpp
#define NewAPI_LogStream_hpp

#include "astris_api.h"
#include "NewApi.hpp"

#include <stdint.h>
#include <iostream>
#include <typeinfo>

namespace AstrisAPI {

// iostream manipulators

struct log_category
{
    log_category(uint64_t cat):
        category(cat)
    {}

    uint64_t category;
};

struct log_domain
{
    log_domain(const char *dom):
        domain(dom)
    {}

    const char *domain;
};

// Integer number formatting helpers for most common cases

template <::std::ostream::fmtflags BASE, unsigned W, char PAD='0', ::std::ostream::fmtflags CASE=0> class NUMBER_FORMATTER;

template <::std::ostream::fmtflags BASE, unsigned W, char PAD, ::std::ostream::fmtflags CASE>
::std::ostream& operator<<(::std::ostream &os, const NUMBER_FORMATTER<BASE,W,PAD,CASE> &number_formatter)
{
    return number_formatter.output_to_stream(os);
}

template <::std::ostream::fmtflags BASE, unsigned W, char PAD, ::std::ostream::fmtflags CASE>
class NUMBER_FORMATTER
{
public:
    NUMBER_FORMATTER(int8_t v): m_value(v), m_size(-static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(int16_t v): m_value(v), m_size(-static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(int32_t v): m_value(v), m_size(-static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(int64_t v): m_value(v), m_size(-static_cast<int>(sizeof(v))) {}

    NUMBER_FORMATTER(uint8_t v): m_value(v), m_size(static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(uint16_t v): m_value(v), m_size(static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(uint32_t v): m_value(v), m_size(static_cast<int>(sizeof(v))) {}
    NUMBER_FORMATTER(uint64_t v): m_value(v), m_size(static_cast<int>(sizeof(v))) {}

    ::std::ostream& output_to_stream(::std::ostream &os) const
    {
        // Save ostream state
        ::std::streamsize previous_width = os.width(W);
        ::std::ostream::fmtflags previous_flags = os.flags(BASE | ::std::ostream::right | CASE);
        char previous_fill = os.fill(PAD);

        switch (m_size) {
        case -1:
            os << static_cast<int8_t>(m_value);
            break;

        case -2:
            os << static_cast<int16_t>(m_value);
            break;

        case -4:
            os << static_cast<int32_t>(m_value);
            break;

        case -8:
            os << static_cast<int64_t>(m_value);
            break;

        case 1:
            os << static_cast<uint8_t>(m_value);
            break;

        case 2:
            os << static_cast<uint16_t>(m_value);
            break;

        case 4:
            os << static_cast<uint32_t>(m_value);
            break;

        case 8:
            os << static_cast<uint64_t>(m_value);
            break;
        }

        // Restore ostream state
        os.fill(previous_fill);
        os.flags(previous_flags);
        os.width(previous_width);

        return os;
    }

private:

    uint64_t m_value;
    int m_size;             // negative size means signed value
};

// Specialized templates for ease of use

// Upper-case HEX with default '0' fill
template <unsigned W, char PAD='0'>
class HEX: public NUMBER_FORMATTER<::std::ostream::hex, W, PAD, ::std::ostream::uppercase>
{
public:
    using NUMBER_FORMATTER<::std::ostream::hex,W,PAD, ::std::ostream::uppercase>::NUMBER_FORMATTER;
};

// Lower-case hex with default '0' fill
template <unsigned W, char PAD='0'>
class HEX_: public NUMBER_FORMATTER<::std::ostream::hex, W, PAD>
{
public:
    using NUMBER_FORMATTER<::std::ostream::hex,W,PAD>::NUMBER_FORMATTER;
};

// Decimal with default '0' fill
template <unsigned W, char PAD='0'>
class DEC: public NUMBER_FORMATTER<::std::ostream::dec, W, PAD>
{
public:
    using NUMBER_FORMATTER<::std::ostream::dec,W,PAD>::NUMBER_FORMATTER;
};

/*

Usage example:

using namespace AstrisAPI;

int main()
{
    Logger::ptr_t astrisLogger = cm.getLogger();
    std::clog.rdbuf(new LoggBuffer(astrisLogger));

    std::clog << "Log message - status" << std::endl;
    std::clog << log_category(AXL_U_ERR) << "Error occurred: " << error << std::endl;
    std::clog << "0x" << HEX<8>(765987) << std::endl;

    return 0;
}

*/

class ASTRIS_EXPORT LogBuffer: public ::std::streambuf
{
public:
    LogBuffer(Logger::ptr_t logger);
    virtual ~LogBuffer();

protected:

    int sync() override;
    int overflow(int c) override;
    ::std::streamsize xsputn(const char* s, std::streamsize n) override;

    void setCategory(uint64_t category);
    void setDomain(const char *domain);
    void setSource(log_source_e source);

    static LogBuffer* fromRdbuf(::std::streambuf *sbuff) { return (sbuff != nullptr && typeid(*sbuff) == s_type_info) ? static_cast<LogBuffer*>(sbuff) : nullptr; }

    static const size_t s_bufferSize = 2048;

private:
    LogBuffer(const LogBuffer&) = delete;
    LogBuffer& operator=(const LogBuffer&) = delete;

    int internalSync();

    Logger::ptr_t m_logger;
    const char *m_domain;
    uint64_t m_category;
    unsigned int m_source_count;
    char m_buffer[s_bufferSize + 1];        // +1 for terminating 0

    static constexpr uint64_t s_default_category = AXL_USER | AXL_OUT | AXL_STA;
    static const ::std::type_info& s_type_info;

    friend ::std::ostream& operator<<(::std::ostream &os, const log_category &cat);
    friend ::std::ostream& operator<<(::std::ostream &os, const log_domain &dom);
    friend ::std::ostream& operator<<(::std::ostream &os, log_source_e srource);
};

::std::ostream& ASTRIS_EXPORT operator<<(::std::ostream &os, const log_category &cat);
::std::ostream& ASTRIS_EXPORT operator<<(::std::ostream &os, const log_domain &dom);
::std::ostream& ASTRIS_EXPORT operator<<(::std::ostream &os, log_source_e source);

} // namespace AstrisAPI

#endif /* NewAPI_LogStream_hpp */
