//
// Copyright (C) 2022 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.
//

#include "codegen_java.h"

#include <expresscatalog-utils.h>
#include <stdio.h>

namespace android {
namespace express {

void writeJavaFilePreamble(FILE* fd) {
    fprintf(fd, "// DO NOT EDIT THIS FILE\n");
    fprintf(fd, "// This is auto-generated file by expresscatalog-codegen\n\n");
}

void writeJavaPackagePreamble(FILE* fd, const std::string& packageName) {
    fprintf(fd, "package %s;\n\n", packageName.c_str());

    fprintf(fd, "import android.util.ArrayMap;\n\n");
    fprintf(fd, "import java.util.InputMismatchException;\n\n");
}

const char* getMetricTypeString(int metricType) {
    static const char* metricTypeStringMap[] = {
            "METRIC_TYPE_UNKNOWN",
            "METRIC_TYPE_COUNTER",
            "METRIC_TYPE_HISTOGRAM",
            "METRIC_TYPE_COUNTER_WITH_UID",
            "METRIC_TYPE_HISTOGRAM_WITH_UID",
    };

    if (metricType == 0) {
        return nullptr;
    }

    return metricTypeStringMap[metricType];
}

void writeJavaHashMapInitFunction(FILE* fd, const MetricInfoMap& metricsIds) {
    fprintf(fd, "private static ArrayMap<String, MetricInfo> metricIds;\n");
    fprintf(fd, "static {\n");
    fprintf(fd, "    metricIds = new ArrayMap<String, MetricInfo>();\n");
    for (const auto& metricInfo : metricsIds) {
        const char* metricTypeString = getMetricTypeString(metricInfo.second.type);
        if (metricTypeString != nullptr) {
            fprintf(fd, "    metricIds.put(\"%s\", new MetricInfo(%ldl, %s));\n",
                    metricInfo.first.c_str(), metricInfo.second.hash, metricTypeString);
        } else {
            LOGE("Metric type is undefined for %s. Termination\n", metricInfo.first.c_str());
            exit(-1);
        }
    }
    fprintf(fd, "}\n\n");
}

bool CodeGeneratorJava::generateCodeImpl(FILE* fd, const MetricInfoMap& metricsIds) const {
    writeJavaFilePreamble(fd);
    writeJavaPackagePreamble(fd, mPackageName);

    fprintf(fd, "public final class %s {\n\n", mClassName.c_str());

    // TODO: auto-generate enum int constants
    fprintf(fd, "public static final int METRIC_TYPE_UNKNOWN = 0;\n");
    fprintf(fd, "public static final int METRIC_TYPE_COUNTER = 1;\n");
    fprintf(fd, "public static final int METRIC_TYPE_HISTOGRAM = 2;\n");
    fprintf(fd, "public static final int METRIC_TYPE_COUNTER_WITH_UID = 3;\n");
    fprintf(fd, "public static final int METRIC_TYPE_HISTOGRAM_WITH_UID = 4;\n\n");

    fprintf(fd, "private static final class MetricInfo {\n");
    fprintf(fd, "   MetricInfo(long hash, int type) {\n");
    fprintf(fd, "       mHash = hash;\n");
    fprintf(fd, "       mType = type;\n");
    fprintf(fd, "   }\n");
    fprintf(fd, "   public long mHash;\n");
    fprintf(fd, "   public int mType;\n");
    fprintf(fd, "}\n\n");

    writeJavaHashMapInitFunction(fd, metricsIds);

    fprintf(fd, "static long getMetricIdHash(String metricId, int type) {\n");
    fprintf(fd, "    MetricInfo info = metricIds.get(metricId);\n");
    fprintf(fd, "    if(info == null) {\n");
    fprintf(fd,
            "        throw new IllegalArgumentException(\"Metric is undefined \" + metricId);\n");
    fprintf(fd, "    }\n");
    fprintf(fd, "    if(info.mType != type) {\n");
    fprintf(fd, "        throw new InputMismatchException(\"Metric type is not \" + type);\n");
    fprintf(fd, "    }\n");
    fprintf(fd, "    return info.mHash;\n");
    fprintf(fd, "}\n\n");

    fprintf(fd, "}\n\n");

    return true;
}

}  // namespace express
}  // namespace android