// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <lib/fidl/coding.h>

#include <inttypes.h>
#include <stdarg.h>
#include <string.h>

#include <lib/fidl/internal.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>

namespace {

class StringBuilder {
public:
    StringBuilder(char* buffer, size_t capacity)
        : buffer_(buffer), capacity_(capacity) {}

    size_t length() const { return length_; }

    void Append(const char* data, size_t length) {
        size_t remaining = capacity_ - length_;
        if (length > remaining) {
            length = remaining;
        }
        memcpy(buffer_ + length_, data, length);
        length_ += length;
    }

    void Append(const char* data) {
        Append(data, strlen(data));
    }

    void AppendPrintf(const char* format, ...) __PRINTFLIKE(2, 3) {
        va_list ap;
        va_start(ap, format);
        AppendVPrintf(format, ap);
        va_end(ap);
    }

    void AppendVPrintf(const char* format, va_list ap) {
        size_t remaining = capacity_ - length_;
        if (remaining == 0u) {
            return;
        }
        int count = vsnprintf(buffer_ + length_, remaining, format, ap);
        if (count <= 0) {
            return;
        }
        size_t length = static_cast<size_t>(count);
        length_ += (length >= remaining ? remaining : length);
    }

private:
    char* buffer_;
    size_t capacity_;
    size_t length_ = 0u;
};

void FormatNullability(StringBuilder* str, fidl::FidlNullability nullable) {
    if (nullable == fidl::kNullable) {
        str->Append("?");
    }
}

void FormatStructName(StringBuilder* str, const fidl::FidlCodedStruct* coded_struct) {
    if (coded_struct->name) {
        str->Append(coded_struct->name);
    } else {
        str->Append("struct");
    }
}

void FormatUnionName(StringBuilder* str, const fidl::FidlCodedUnion* coded_union) {
    if (coded_union->name) {
        str->Append(coded_union->name);
    } else {
        str->Append("union");
    }
}

void FormatTypeName(StringBuilder* str, const fidl_type_t* type);
void FormatElementName(StringBuilder* str, const fidl_type_t* type) {
    if (type) {
        FormatTypeName(str, type);
    } else {
        // TODO(jeffbrown): Print the actual primitive type name, assuming we
        // start recording that information in the tables.
        str->Append("primitive");
    }
}

void FormatTypeName(StringBuilder* str, const fidl_type_t* type) {
    switch (type->type_tag) {
    case fidl::kFidlTypeStruct:
        FormatStructName(str, &type->coded_struct);
        break;
    case fidl::kFidlTypeStructPointer:
        FormatStructName(str, type->coded_struct_pointer.struct_type);
        str->Append("?");
        break;
    case fidl::kFidlTypeUnion:
        FormatUnionName(str, &type->coded_union);
        break;
    case fidl::kFidlTypeUnionPointer:
        FormatUnionName(str, type->coded_union_pointer.union_type);
        str->Append("?");
        break;
    case fidl::kFidlTypeArray:
        str->Append("array<");
        FormatElementName(str, type->coded_array.element);
        str->Append(">");
        str->AppendPrintf(":%" PRIu32, type->coded_array.array_size /
                                           type->coded_array.element_size);
        break;
    case fidl::kFidlTypeString:
        str->Append("string");
        if (type->coded_string.max_size != FIDL_MAX_SIZE) {
            str->AppendPrintf(":%" PRIu32, type->coded_string.max_size);
        }
        FormatNullability(str, type->coded_string.nullable);
        break;
    case fidl::kFidlTypeHandle:
        str->Append("handle");
        if (type->coded_handle.handle_subtype) {
            str->Append("<");
            switch (type->coded_handle.handle_subtype) {
            case fidl::kFidlHandleSubtypeHandle:
                str->Append("handle");
                break;
            case fidl::kFidlHandleSubtypeProcess:
                str->Append("process");
                break;
            case fidl::kFidlHandleSubtypeThread:
                str->Append("thread");
                break;
            case fidl::kFidlHandleSubtypeVmo:
                str->Append("vmo");
                break;
            case fidl::kFidlHandleSubtypeChannel:
                str->Append("channel");
                break;
            case fidl::kFidlHandleSubtypeEvent:
                str->Append("event");
                break;
            case fidl::kFidlHandleSubtypePort:
                str->Append("port");
                break;
            case fidl::kFidlHandleSubtypeInterrupt:
                str->Append("interrupt");
                break;
            case fidl::kFidlHandleSubtypeLog:
                str->Append("log");
                break;
            case fidl::kFidlHandleSubtypeSocket:
                str->Append("socket");
                break;
            case fidl::kFidlHandleSubtypeResource:
                str->Append("resource");
                break;
            case fidl::kFidlHandleSubtypeEventpair:
                str->Append("eventpair");
                break;
            case fidl::kFidlHandleSubtypeJob:
                str->Append("job");
                break;
            case fidl::kFidlHandleSubtypeVmar:
                str->Append("vmar");
                break;
            case fidl::kFidlHandleSubtypeFifo:
                str->Append("fifo");
                break;
            case fidl::kFidlHandleSubtypeGuest:
                str->Append("guest");
                break;
            case fidl::kFidlHandleSubtypeTimer:
                str->Append("timer");
                break;
            // TODO(pascallouis): Add support for iomap, pci, and hypervisor
            // when they are supported in FIDL.
            default:
                str->AppendPrintf("%" PRIu32, type->coded_handle.handle_subtype);
                break;
            }
            str->Append(">");
        }
        FormatNullability(str, type->coded_handle.nullable);
        break;
    case fidl::kFidlTypeVector:
        str->Append("vector<");
        FormatElementName(str, type->coded_vector.element);
        str->Append(">");
        if (type->coded_vector.max_count != FIDL_MAX_SIZE) {
            str->AppendPrintf(":%" PRIu32, type->coded_vector.max_count);
        }
        FormatNullability(str, type->coded_vector.nullable);
        break;
    default:
        ZX_PANIC("unrecognized tag");
        break;
    }
}

} // namespace

size_t fidl_format_type_name(const fidl_type_t* type,
                             char* buffer, size_t capacity) {
    if (!type || !buffer || !capacity) {
        return 0u;
    }

    StringBuilder str(buffer, capacity);
    FormatTypeName(&str, type);
    return str.length();
}