/*
 * Copyright (C) 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
 */

#pragma once

#include <wtf/PrintStream.h>
#include <wtf/StringPrintStream.h>
#include <wtf/Vector.h>

namespace JSC {

namespace Probe {
class Context;
} // namespace Probe

namespace Printer {

struct Context;

union Data {
    Data()
    {
        const intptr_t uninitialized = 0xdeadb0d0;
        memcpy(&buffer, &uninitialized, sizeof(uninitialized));
    }
    Data(uintptr_t value)
        : Data(&value, sizeof(value))
    { }
    Data(const void* pointer)
        : Data(&pointer, sizeof(pointer))
    { }
    Data(void* src, size_t size)
    {
        RELEASE_ASSERT(size <= sizeof(buffer));
        memcpy(&buffer, src, size);
    }

    template<typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
    T as() const
    {
        return static_cast<T>(value);
    }

    template<typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type>
    const T as(int = 0) const
    {
        return reinterpret_cast<const T>(pointer);
    }

    template<typename T, typename = typename std::enable_if<!std::is_integral<T>::value && !std::is_pointer<T>::value>::type>
    const T& as() const
    {
        static_assert(sizeof(T) <= sizeof(buffer), "size is not sane");
        return *reinterpret_cast<const T*>(&buffer);
    }

    uintptr_t value;
    const void* pointer;
#if USE(JSVALUE64)
    uintptr_t buffer[4];
#elif USE(JSVALUE32_64)
    uintptr_t buffer[6];
#endif
};

struct Context {
    Context(Probe::Context& probeContext, Data& data)
        : probeContext(probeContext)
        , data(data)
    { }

    Probe::Context& probeContext;
    Data& data;
};

typedef void (*Callback)(PrintStream&, Context&);

struct PrintRecord {
    PrintRecord(Data data, Callback printer)
        : data(data)
        , printer(printer)
    { }

    PrintRecord(Callback printer)
        : printer(printer)
    { }

    template<template<class> class Printer, typename T>
    PrintRecord(const Printer<T>& other)
    {
        static_assert(std::is_base_of<PrintRecord, Printer<T>>::value, "Printer should extend PrintRecord");
        static_assert(sizeof(PrintRecord) == sizeof(Printer<T>), "Printer should be the same size as PrintRecord");
        data = other.data;
        printer = other.printer;
    }

    Data data;
    Callback printer;

protected:
    PrintRecord() { }
};

template<typename T> struct Printer;

typedef Vector<PrintRecord> PrintRecordList;

inline void appendPrinter(PrintRecordList&) { }

template<typename First, typename... Arguments>
inline void appendPrinter(PrintRecordList& printRecordList, First first, Arguments&&... others)
{
    printRecordList.append(Printer<First>(first));
    appendPrinter(printRecordList, std::forward<Arguments>(others)...);
}

template<typename... Arguments>
inline PrintRecordList* makePrintRecordList(Arguments&&... arguments)
{
    // FIXME: the current implementation intentionally leaks the PrintRecordList.
    // We may want to fix this in the future if we want to use the print mechanism
    // in tests that may compile a lot of prints.
    // https://bugs.webkit.org/show_bug.cgi?id=171123
    auto printRecordList = new PrintRecordList();
    appendPrinter(*printRecordList, std::forward<Arguments>(arguments)...);
    return printRecordList;
}

// Some utility functions for specializing printers.

void printConstCharString(PrintStream&, Context&);
void printIntptr(PrintStream&, Context&);
void printUintptr(PrintStream&, Context&);
void printPointer(PrintStream&, Context&);

void setPrinter(PrintRecord&, CString&&);

// Specialized printers.

template<>
struct Printer<const char*> : public PrintRecord {
    Printer(const char* str)
        : PrintRecord(str, printConstCharString)
    { }
};

template<>
struct Printer<char*> : public Printer<const char*> {
    Printer(char* str)
        : Printer<const char*>(str)
    { }
};

template<>
struct Printer<RawPointer> : public PrintRecord {
    Printer(RawPointer rawPointer)
        : PrintRecord(rawPointer.value(), printPointer)
    { }
};

template<typename T, typename = typename std::enable_if_t<std::is_integral<T>::value && std::numeric_limits<T>::is_signed>>
void setPrinter(PrintRecord& record, T value, intptr_t = 0)
{
    record.data.value = static_cast<uintptr_t>(value);
    record.printer = printIntptr;
}

template<typename T, typename = typename std::enable_if_t<std::is_integral<T>::value && !std::numeric_limits<T>::is_signed>>
void setPrinter(PrintRecord& record, T value, uintptr_t = 0)
{
    record.data.value = static_cast<uintptr_t>(value);
    record.printer = printUintptr;
}

template<typename T>
struct Printer : public PrintRecord {
    Printer(T value)
    {
        setPrinter(*this, value);
    }
};

} // namespace Printer

} // namespace JSC
