/*
 * Copyright (C) 2023 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.
 */
package com.android.tradefed.build.content;

import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/** This describes the structure of the content and its descriptor as generated by the CAS tool */
public class ArtifactDetails {
    public String artifact; // Name of field has to match the cas tool output
    public List<ArtifactFileDescriptor> details; // Name of field has to match the cas tool output

    public static class ArtifactFileDescriptor {
        public String digest;
        public String path;
        public long size;
    }

    public static ArtifactDetails parseFile(File input, String targetArtifact) throws IOException {
        return parseFile(input, targetArtifact, null, null);
    }

    /** Parses cas_content_details.json and extract information for the entry considered. */
    public static ArtifactDetails parseFile(
            File input, String targetArtifact, String baseBuildId, String currentBuildId)
            throws IOException {
        try (CloseableTraceScope ignored = new CloseableTraceScope("parse_artifacts_details")) {
            Gson gson = new Gson();
            JsonArray mainArray = null;
            try (BufferedReader reader = new BufferedReader(new FileReader(input))) {
                mainArray = gson.fromJson(reader, JsonArray.class);
            }
            String namedArtifact = targetArtifact;
            if (currentBuildId != null && targetArtifact.contains(currentBuildId)) {
                namedArtifact = namedArtifact.replace(currentBuildId, baseBuildId);
            }
            for (JsonElement e : mainArray) {
                JsonObject o = e.getAsJsonObject();
                JsonElement name = o.asMap().get("artifact");
                if (name.getAsString().equals(namedArtifact)) {
                    ArtifactDetails d = gson.fromJson(e, ArtifactDetails.class);
                    if (d == null) {
                        throw new RuntimeException("Failed to parse for content.");
                    }
                    return d;
                }
            }
            throw new RuntimeException(namedArtifact + " entry was not found.");
        }
    }

    /** Obtain the list of modification between a base and the current build contents. */
    public static List<ArtifactFileDescriptor> diffContents(
            ArtifactDetails base, ArtifactDetails current) {
        try (CloseableTraceScope ignored = new CloseableTraceScope("diff_contents")) {
            if (!base.artifact.equals(current.artifact)) {
                CLog.w(
                        "Not comparing the same artifact entries ! %s != %s",
                        base.artifact, current.artifact);
            }
            Map<String, ArtifactFileDescriptor> mappingBase =
                    base.details.stream()
                            .map(e -> e)
                            .collect(Collectors.toMap(e -> e.path, e -> e));
            Map<String, ArtifactFileDescriptor> mappingCurrent =
                    current.details.stream()
                            .map(e -> e)
                            .collect(Collectors.toMap(e -> e.path, e -> e));
            List<ArtifactFileDescriptor> affected = new ArrayList<>();
            int modified = 0;
            int deleted = 0;
            int added = 0;
            int unchanged = 0;
            for (ArtifactFileDescriptor be : base.details) {
                if (mappingCurrent.containsKey(be.path)) {
                    ArtifactFileDescriptor pe = mappingCurrent.get(be.path);
                    if (!be.digest.equals(pe.digest)) {
                        CLog.d("diff: %s", be.path);
                        modified++;
                        affected.add(pe);
                    } else {
                        unchanged++;
                    }
                } else {
                    affected.add(be);
                    CLog.d("deleted: %s", be.path);
                    deleted++;
                }
            }
            for (ArtifactFileDescriptor pe : current.details) {
                if (!mappingBase.containsKey(pe.path)) {
                    CLog.d("added: %s", pe.path);
                    added++;
                    affected.add(pe);
                }
            }
            CLog.d(
                    "Summary: unchanged:%s, modified:%s, deleted:%s, added:%s",
                    unchanged, modified, deleted, added);
            return affected;
        }
    }
}
