/*
 * Copyright (c) 2013-2021 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.model.bytecode;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING_TRIVIEW;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT;

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

import com.chrisnewland.freelogj.Logger;
import com.chrisnewland.freelogj.LoggerFactory;

public class SourceMapper
{
	private static final Logger logger = LoggerFactory.getLogger(SourceMapper.class);

	private static Map<String, List<ClassBC>> sourceToClassMap = new HashMap<>();

	public static void clear()
	{
		sourceToClassMap.clear();
	}

	private static String getFullyQualifiedSourceName(ClassBC classBytecode)
	{
		StringBuilder builder = new StringBuilder();

		String packageName = classBytecode.getPackageName();

		if (packageName != null && packageName.length() > 0)
		{
			builder.append(packageName).append(S_DOT);
		}

		builder.append(classBytecode.getSourceFile());

		return builder.toString();
	}

	public static void addSourceClassMapping(ClassBC classBytecode)
	{	
		String fqName = getFullyQualifiedSourceName(classBytecode);
	
		List<ClassBC> classBytecodeList = sourceToClassMap.get(fqName);

		if (classBytecodeList == null)
		{
			classBytecodeList = new ArrayList<>();

			sourceToClassMap.put(fqName, classBytecodeList);
		}

		classBytecodeList.add(classBytecode);
	}

	public static List<ClassBC> getClassBytecodeList(ClassBC classBytecode)
	{
		String fqName = getFullyQualifiedSourceName(classBytecode);

		List<ClassBC> result = sourceToClassMap.get(fqName);

		if (result == null)
		{
			result = new ArrayList<>();
		}

		return Collections.unmodifiableList(result);
	}

	public static MemberBytecode getMemberBytecodeForSourceLine(ClassBC classBytecode, int sourceLine)
	{		
		MemberBytecode result = null;

		String fqName = getFullyQualifiedSourceName(classBytecode);

		List<ClassBC> classBytecodeList = sourceToClassMap.get(fqName);

		if (classBytecodeList != null)
		{
			if (DEBUG_LOGGING_TRIVIEW)
			{
				logger.debug("Found {} ClassBC for source {}", classBytecodeList.size(), fqName);
			}

			outer: for (ClassBC classBC : classBytecodeList)
			{
				for (MemberBytecode tempMemberBytecode : classBC.getMemberBytecodeList())
				{
					LineTable lineTable = tempMemberBytecode.getLineTable();

					if (DEBUG_LOGGING_TRIVIEW)
					{
						logger.debug("Checking LineTable\n{}", lineTable.toString());
					}

					if (lineTable.sourceLineInRange(sourceLine))
					{
						result = tempMemberBytecode;
						break outer;
					}
				}
			}
		}
		else
		{
			logger.warn("No source-bytecode mapping found for class {}", classBytecode.getFullyQualifiedClassName());
		}

		if (DEBUG_LOGGING_TRIVIEW)
		{
			logger.debug("Found bytecode offset {}", result);
		}

		return result;
	}

	public static int getSourceLineFromBytecode(MemberBytecode memberBytecode, int bytecodeOffset)
	{
		int result = -1;

		if (memberBytecode != null)
		{
			LineTable lineTable = memberBytecode.getLineTable();
			
			result = lineTable.findSourceLineForBytecodeOffset(bytecodeOffset);
		}

		return result;
	}
}
