/*

SPDX-FileCopyrightText: Copyright 2021 Siemens
SPDX-License: GPL-3.0-only
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.


 */

package javareporter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class ApiStorage {

	public ApiStorage()
	{
		Reset();
	}
	
	public void Reset()
	{
		ApiTypeToNameMap =  new HashMap<String, Map<String, Map<String, List<ApiEntry>>>>();
		tempApiTypeToNameMap = new HashMap<String, Map<String, Map<String, List<ApiEntry>>>>();
	}
	
	public enum ApiType
	{
	    TYPE,
	    FIELD,
	    METHOD,
	    CONSTRUCTOR,
	    PACKAGE,
	    MODULE
	    
	}

	private String EscapeCharactersForJson(String master)
	{

		//replace " with \"
		master = master.replace("\"", "\\\"");
        //replace \ with \\
		master = master.replace("\\", "\\\\");
		
		//if we over escape things after the first two steps, i.e. \\" or \\\
		//this can happen if there is a string in a line with already escaped characters
		master = master.replace("\\\\\"", "\\\"");
		master = master.replace("\\\\\\", "\\\\");
		
		return master;
	}
	
	
	class ApiEntry
	{
		public ApiEntry(String name, String classFileName){
			ApiName = EscapeCharactersForJson(name);
			ClassFile = classFileName;
		}
		
		
		public void SetUsageString(String usage)
		{
			Usage = EscapeCharactersForJson(usage);
			
		}
		public String toString()
		{
			return ApiName + "|" + ClassFile + "|" + Usage;
		}
		
		public String getApiName()
		{
			return ApiName;
		}
		
		public String getClassFile()
		{
			return ClassFile;
		}
		
		public String getUsage()
		{
			return Usage;
		}
		private String ApiName = "";
		private String ClassFile = "";
		private String Usage = "";
	}
	
	//NX Version -> Class name -> ApiName -> List of ApiEntrys
	Map<String, Map<String, Map<String, List<ApiEntry>>>> ApiTypeToNameMap = null;
	Map<String, Map<String, Map<String, List<ApiEntry>>>> tempApiTypeToNameMap = null;
	String currentLine = "";
	
	private void mergeTempMapIntoFullMap()
	{
		for (Map.Entry<String, Map<String,  Map<String, List<ApiEntry>>>> VersionEntry : tempApiTypeToNameMap.entrySet()) {
			for (Map.Entry<String,  Map<String, List<ApiEntry>>> entry : VersionEntry.getValue().entrySet()) {
	    	    
	    	    for (Map.Entry<String, List<ApiEntry>> entry2 : entry.getValue().entrySet())
	    	    {
	    	    	 List<ApiEntry> entries = entry2.getValue();
	    	    	 
	    	    	 for (ApiEntry toUpdate : entries)
	    	    	 {
	    	    		 toUpdate.SetUsageString(currentLine);
	    	    	 }
	    	    	 
	    	    	 
	    	    	 Map<String, Map<String, List<ApiEntry>>> apiTypesToNames = ApiTypeToNameMap.get(VersionEntry.getKey());
	    	 		if (apiTypesToNames == null)
	    	 		{
	    	 			apiTypesToNames = new HashMap<String, Map<String, List<ApiEntry>>>();
	    	 			
	    	 		}
	    	 		
	    	 		
	    	 	    Map<String, List<ApiEntry>> masterentries = apiTypesToNames.get(entry.getKey());
	    	 	     
	    	 	    if (masterentries == null)
	    	 	    {
	    	 	    	masterentries = new HashMap<String, List<ApiEntry>>();
	    	 	    	
	    	 	    }
	    	 	    
	    	 	    List<ApiEntry> apiEntries = masterentries.get(entry2.getKey());
	    	 	    
	    	 	    if (apiEntries == null)
	    	 	    {
	    	 		    apiEntries = new ArrayList<ApiEntry>();
	    	 	    }
	    	 	    
	    	 	    apiEntries.addAll(entries);
	    	 	    masterentries.put(entry2.getKey(), apiEntries);
	    	 	    apiTypesToNames.put(entry.getKey(), masterentries);
	    	 	    ApiTypeToNameMap.put(VersionEntry.getKey(), apiTypesToNames);
	    	    	 
	    	    }
	    	    
	    	}
		}
		currentLine = "";
		tempApiTypeToNameMap = new HashMap<String, Map<String, Map<String, List<ApiEntry>>>>();
	}
	
	
	
	public ApiType GetApiType(int typeval) {
		ApiType type = null;
		if (typeval == 1)
		{
			type = ApiType.TYPE;
		}
		else if (typeval == 2)
		{
			type = ApiType.FIELD;
		}
		else if (typeval == 3)
		{
			type = ApiType.METHOD;
		}
		else if (typeval == 4)
		{
			type = ApiType.CONSTRUCTOR;
		}
		else if (typeval == 5)
		{
			type = ApiType.PACKAGE;
		}
		else if (typeval == 6)
		{
			type = ApiType.MODULE;
		}
		return type;
	}
	
	public void AppendToCurrentLine(String s, boolean endOfLine) 
	{
		currentLine += s;
		if (endOfLine)
		{
			mergeTempMapIntoFullMap();
		}
	}
	
	public Map<String, Map<String, Map<String, List<ApiEntry>>>> GetMap()
	{
		return ApiTypeToNameMap;
		
	}
	
	private String ConvertPrimitiveType(String descriptor)
	{
		if (descriptor.equals("Z"))
		{
			return "boolean";
		}
		else if (descriptor.equals("B"))
		{
			return "byte";
		}
		else if (descriptor.equals("C"))
		{
			return "char";
		}
		else if (descriptor.equals("D"))
		{
			return "double";
		}
		else if (descriptor.equals("F"))
		{
			return "float";
		}
		else if (descriptor.equals("I"))
		{
			return "int";
		}
		else if (descriptor.equals("J"))
		{
			return "long";
		}
		else if (descriptor.equals("S"))
		{
			return "short";
		}
		else if (descriptor.equals("V"))
		{
			return "void";
		}
		else
		{
			return descriptor;
		}
	}
	
	private String FormatArgs(String descriptor, String formattedName)
	{
	    String formattedArgs = "";
	    String tempdescriptor = descriptor;
	    tempdescriptor = tempdescriptor.replace("/", ".").replace("$", ".");
	    
	    if (tempdescriptor.contains(")"))
	    {
	    	String[] ArgsAndReturnVal = tempdescriptor.split("\\)");
	    	
	    	//take off the "L" at the beginning of the return value
	    	String returnVal = ArgsAndReturnVal[1].replace(";", "");
	    	boolean isArray = false;
	    	if (returnVal.startsWith("["))
	    	{
	    		returnVal = returnVal.substring(1).replace(";", "");
	    		isArray = true;
	    	}
	    	
	    	if (returnVal.startsWith("L"))
	    	{
	    		returnVal = returnVal.substring(1).replace(";", "");
	    	}
	    	
	    	returnVal = ConvertPrimitiveType(returnVal);
	    	
	    	if (isArray)
	    	{
	    		returnVal += "[]";
	    	}
	    	
	    	formattedArgs += returnVal + " " + formattedName;
	    	
	    	//remove ( and : from the string so it's easier to check parameter types
	    	String[] parameters;
	    	if (ArgsAndReturnVal[0].contains("["))
	    	{
	    		parameters = ArgsAndReturnVal[0].replace("(","").replace(";", "").split("\\[");
	    	}
	    	else
	    	{
	    		parameters = ArgsAndReturnVal[0].replace("(","").split(";");
	    	}
	    	formattedArgs += "(";
    		for (int i= 0; i < parameters.length; i++)
    		{
    			if (i != 0 && !parameters[i].equals(""))
    			{
    				formattedArgs += ",";
    			}
    			if (parameters[i].toUpperCase().equals(parameters[i]) && parameters[i].length() > 1)
    			{
    				//if the parameter is completely uppercase, have to manually separate into primitive types for some cases
    				for (int j = 0; j <parameters[i].length(); j++)
    				{
    					//looks complicated, but is iterating through the characters in the string parameters[i]
    					//and adding them to formatted args as separate parameters
    					if (j!= 0)
    					{
    						formattedArgs += ",";
    					}
    					formattedArgs += ConvertPrimitiveType(Character.toString(parameters[i].toCharArray()[j]));
    				}
    			}
    			else if (parameters[i].startsWith("L"))
    			{
    				formattedArgs += ConvertPrimitiveType(parameters[i].substring(1));
    			}
    			else
    			{
    				formattedArgs += ConvertPrimitiveType(parameters[i]);
    			}
    			
    		}
    		formattedArgs += ")";
    	}
	    else
	    {
	    	formattedArgs +=formattedName;
	    }
	    
	    
	    
	    return formattedArgs;
	    	
	}
	//add any NX types we come across to the temp map so that the
	//entire line of code can be built up before adding to the full map
	public void AddToMap(int typeval, String fullyQualifiedName, String classfilename, String currentClass, String descriptor)
	{
		String fullname = ConvertPrimitiveType(fullyQualifiedName);
		classfilename = EscapeCharactersForJson(classfilename);
		
		if (descriptor != null)
		{
			fullname = FormatArgs(descriptor, fullyQualifiedName);
		}
		
		Map<String, Map<String, List<ApiEntry>>> VersionToApiMap = tempApiTypeToNameMap.get(Utils.GetFromVersion());
		if (VersionToApiMap == null)
		{
			VersionToApiMap = new HashMap<String, Map<String, List<ApiEntry>>>();
			
		}
		
		Map<String, List<ApiEntry>> apiTypesToNames = VersionToApiMap.get(classfilename);
		if (apiTypesToNames == null)
		{
			apiTypesToNames = new HashMap<String, List<ApiEntry>>();
			
		}
		
	     List<ApiEntry> entries = apiTypesToNames.get(fullname);
	     
	     if (entries == null)
	     {
	    	 entries = new ArrayList<ApiEntry>();
	    	
	     }
	     entries.add(new ApiEntry(fullname, currentClass));
	     apiTypesToNames.put(fullname, entries);
	     VersionToApiMap.put(classfilename, apiTypesToNames);

	     tempApiTypeToNameMap.put(Utils.GetFromVersion(), VersionToApiMap);
	     
	}
	
}
