/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/dsl/DSLType.h"

#include "src/sksl/dsl/priv/DSLWriter.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLStructDefinition.h"

namespace SkSL {

namespace dsl {

const SkSL::Type& DSLType::skslType() const {
    if (fSkSLType) {
        return *fSkSLType;
    }
    const SkSL::Context& context = DSLWriter::Context();
    switch (fTypeConstant) {
        case kBool:
            return *context.fTypes.fBool;
        case kBool2:
            return *context.fTypes.fBool2;
        case kBool3:
            return *context.fTypes.fBool3;
        case kBool4:
            return *context.fTypes.fBool4;
        case kHalf:
            return *context.fTypes.fHalf;
        case kHalf2:
            return *context.fTypes.fHalf2;
        case kHalf3:
            return *context.fTypes.fHalf3;
        case kHalf4:
            return *context.fTypes.fHalf4;
        case kFloat:
            return *context.fTypes.fFloat;
        case kFloat2:
            return *context.fTypes.fFloat2;
        case kFloat3:
            return *context.fTypes.fFloat3;
        case kFloat4:
            return *context.fTypes.fFloat4;
        case kInt:
            return *context.fTypes.fInt;
        case kInt2:
            return *context.fTypes.fInt2;
        case kInt3:
            return *context.fTypes.fInt3;
        case kInt4:
            return *context.fTypes.fInt4;
        case kShort:
            return *context.fTypes.fShort;
        case kShort2:
            return *context.fTypes.fShort2;
        case kShort3:
            return *context.fTypes.fShort3;
        case kShort4:
            return *context.fTypes.fShort4;
        case kVoid:
            return *context.fTypes.fVoid;
        default:
            SkUNREACHABLE;
    }
}

static DSLExpression construct1(const SkSL::Type& type, DSLExpression a) {
    std::vector<DSLExpression> args;
    args.push_back(std::move(a));
    return DSLWriter::Construct(type, std::move(args));
}

static DSLExpression construct2(const SkSL::Type& type, DSLExpression a,
                                DSLExpression b) {
    std::vector<DSLExpression> args;
    args.push_back(std::move(a));
    args.push_back(std::move(b));
    return DSLWriter::Construct(type, std::move(args));
}

static DSLExpression construct3(const SkSL::Type& type, DSLExpression a,
                                DSLExpression b,
                                DSLExpression c) {
    std::vector<DSLExpression> args;
    args.push_back(std::move(a));
    args.push_back(std::move(b));
    args.push_back(std::move(c));
    return DSLWriter::Construct(type, std::move(args));
}

static DSLExpression construct4(const SkSL::Type& type, DSLExpression a, DSLExpression b,
                                DSLExpression c, DSLExpression d) {
    std::vector<DSLExpression> args;
    args.push_back(std::move(a));
    args.push_back(std::move(b));
    args.push_back(std::move(c));
    args.push_back(std::move(d));
    return DSLWriter::Construct(type, std::move(args));
}

#define TYPE(T)                                                                                    \
DSLExpression T(DSLExpression a) {                                                                 \
    return construct1(*DSLWriter::Context().fTypes.f ## T, std::move(a));                          \
}                                                                                                  \
DSLExpression T ## 2(DSLExpression a) {                                                            \
    return construct1(*DSLWriter::Context().fTypes.f ## T ## 2, std::move(a));                     \
}                                                                                                  \
DSLExpression T ## 2(DSLExpression a, DSLExpression b) {                                           \
    return construct2(*DSLWriter::Context().fTypes.f ## T ## 2, std::move(a),                      \
                      std::move(b));                                                               \
}                                                                                                  \
DSLExpression T ## 3(DSLExpression a) {                                                            \
    return construct1(*DSLWriter::Context().fTypes.f ## T ## 3, std::move(a));                     \
}                                                                                                  \
DSLExpression T ## 3(DSLExpression a, DSLExpression b) {                                           \
    return construct2(*DSLWriter::Context().fTypes.f ## T ## 3, std::move(a),                      \
                      std::move(b));                                                               \
}                                                                                                  \
DSLExpression T ## 3(DSLExpression a, DSLExpression b, DSLExpression c) {                          \
    return construct3(*DSLWriter::Context().fTypes.f ## T ## 3, std::move(a),                      \
                      std::move(b), std::move(c));                                                 \
}                                                                                                  \
DSLExpression T ## 4(DSLExpression a) {                                                            \
    return construct1(*DSLWriter::Context().fTypes.f ## T ## 4, std::move(a));                     \
}                                                                                                  \
DSLExpression T ## 4(DSLExpression a, DSLExpression b) {                                           \
    return construct2(*DSLWriter::Context().fTypes.f ## T ## 4, std::move(a),                      \
                      std::move(b));                                                               \
}                                                                                                  \
DSLExpression T ## 4(DSLExpression a, DSLExpression b, DSLExpression c) {                          \
    return construct3(*DSLWriter::Context().fTypes.f ## T ## 4, std::move(a), std::move(b),        \
                      std::move(c));                                                               \
}                                                                                                  \
DSLExpression T ## 4(DSLExpression a, DSLExpression b, DSLExpression c, DSLExpression d) {         \
    return construct4(*DSLWriter::Context().fTypes.f ## T ## 4, std::move(a), std::move(b),        \
                      std::move(c), std::move(d));                                                 \
}

TYPE(Bool)
TYPE(Float)
TYPE(Half)
TYPE(Int)
TYPE(Short)

#undef TYPE

DSLType Array(const DSLType& base, int count) {
    SkASSERT(count >= 1);
    return DSLWriter::SymbolTable()->addArrayDimension(&base.skslType(), count);
}

DSLType Struct(const char* name, SkTArray<DSLField> fields) {
    std::vector<SkSL::Type::Field> skslFields;
    skslFields.reserve(fields.count());
    for (const DSLField& field : fields) {
        skslFields.emplace_back(field.fModifiers.fModifiers, field.fName, &field.fType.skslType());
    }
    const SkSL::Type* result = DSLWriter::SymbolTable()->add(Type::MakeStructType(/*offset=*/-1,
                                                                                  name,
                                                                                  skslFields));
    DSLWriter::ProgramElements().push_back(std::make_unique<SkSL::StructDefinition>(/*offset=*/-1,
                                                                                    *result));
    return result;
}

} // namespace dsl

} // namespace SkSL
