// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.dev.tool.docs;

import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;

/**
 * Checks if API status of equals/hashCode is same with its containing class.
 *
 * @author Yoshito
 */
public class APIStatusConsistencyChecker {
    public static void main(String[] args) {
        // args[0]  API signature file path
        // args[1]  (Optional) List of classes to be skipped, separated by semicolon
        if (args.length < 1) {
            System.err.println("Missing API signature file path.");
        } else if (args.length > 2) {
            System.err.println("Too many command arguments");
        }

        List<String> skipClasses = Collections.emptyList();
        if (args.length == 2) {
            String[] classes = args[1].split(";");
            skipClasses = Arrays.asList(classes);
        }

        // Load the ICU4J API signature file
        Set<APIInfo> apiInfoSet = APIData.read(new File(args[0]), true).getAPIInfoSet();
        APIStatusConsistencyChecker checker = new APIStatusConsistencyChecker(apiInfoSet, skipClasses, new PrintWriter(System.err, true));
        checker.checkConsistency();
        System.exit(checker.errCount);
   }

    private int errCount = 0;
    private Set<APIInfo> apiInfoSet;
    private PrintWriter pw;
    private List<String> skipClasses;

    public APIStatusConsistencyChecker(Set<APIInfo> apiInfoSet, List<String> skipClasses, PrintWriter pw) {
        this.apiInfoSet = apiInfoSet;
        this.skipClasses = skipClasses;
        this.pw = pw;
    }

    public int errorCount() {
        return errCount;
    }

    // Methods that should have same API status with a containing class
    static final String[][] METHODS = {
          //{"<method name>",   "<method signature in APIInfo data>"},
            {"equals",      "boolean(java.lang.Object)"},
            {"hashCode",    "int()"},
            {"toString",    "java.lang.String()"},
            {"clone",       "java.lang.Object()"},
    };

    public void checkConsistency() {
        Map<String, APIInfo> classMap = new TreeMap<>();
        // Build a map of APIInfo for classes, indexed by class name
        for (APIInfo api : apiInfoSet) {
            if (!api.isPublic() && !api.isProtected()) {
                continue;
            }
            if (!api.isClass() && !api.isEnum()) {
                continue;
            }
            String fullClassName = api.getPackageName() + "." + api.getName();
            classMap.put(fullClassName, api);
        }

        // Walk through methods
        for (APIInfo api : apiInfoSet) {
            if (!api.isMethod()) {
                continue;
            }

            String fullClassName = api.getPackageName() + "." + api.getClassName();
            if (skipClasses.contains(fullClassName)) {
                continue;
            }

            boolean checkWithClass = false;
            String methodName = api.getName();
            String methodSig = api.getSignature();

            for (String[] method : METHODS) {
                if (method[0].equals(methodName) && method[1].equals(methodSig)) {
                    checkWithClass = true;
                }
            }

            if (!checkWithClass) {
                continue;
            }

            // Check if this method has same API status with the containing class
            APIInfo clsApi = classMap.get(fullClassName);
            if (clsApi == null) {
                pw.println("## Error ## Class " + fullClassName + " is not found.");
                errCount++;
            }

            int methodStatus = api.getVal(APIInfo.STA);
            String methodVer = api.getStatusVersion();
            int classStatus = clsApi.getVal(APIInfo.STA);
            String classVer = clsApi.getStatusVersion();

            if (methodStatus != classStatus || !Objects.equals(methodVer, classVer)) {
                pw.println("## Error ## " + methodName + " in " + fullClassName);
                errCount++;
            }
        }
    }
}
