/*******************************************************************************
 * Copyright (c) 2000, 2008 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     QNX Software Systems - Initial API and implementation
 *     Alena Laskavaia (QNX) - Bug 221224
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.core.cdi;

import java.math.BigInteger;
import java.util.StringTokenizer;

import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.model.ICDIInstruction;
import org.eclipse.cdt.debug.core.cdi.model.ICDIMixedInstruction;
import org.eclipse.cdt.debug.mi.core.GDBTypeParser;
import org.eclipse.cdt.debug.mi.core.MIException;
import org.eclipse.cdt.debug.mi.core.MISession;
import org.eclipse.cdt.debug.mi.core.RxThread;
import org.eclipse.cdt.debug.mi.core.GDBTypeParser.GDBDerivedType;
import org.eclipse.cdt.debug.mi.core.GDBTypeParser.GDBType;
import org.eclipse.cdt.debug.mi.core.cdi.model.Instruction;
import org.eclipse.cdt.debug.mi.core.cdi.model.MixedInstruction;
import org.eclipse.cdt.debug.mi.core.cdi.model.StackFrame;
import org.eclipse.cdt.debug.mi.core.cdi.model.Target;
import org.eclipse.cdt.debug.mi.core.cdi.model.Thread;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.ArrayType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.BoolType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.CharType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.DerivedType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.DoubleType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.EnumType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.FloatType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.FunctionType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.IntType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.LongLongType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.LongType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.PointerType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.ReferenceType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.ShortType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.StructType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.Type;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.VoidType;
import org.eclipse.cdt.debug.mi.core.cdi.model.type.WCharType;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.MIDataDisassemble;
import org.eclipse.cdt.debug.mi.core.command.MIEnvironmentDirectory;
import org.eclipse.cdt.debug.mi.core.command.MIGDBShowDirectories;
import org.eclipse.cdt.debug.mi.core.command.CLIPType;
import org.eclipse.cdt.debug.mi.core.command.CLIWhatis;
import org.eclipse.cdt.debug.mi.core.output.MIAsm;
import org.eclipse.cdt.debug.mi.core.output.MIDataDisassembleInfo;
import org.eclipse.cdt.debug.mi.core.output.MIGDBShowDirectoriesInfo;
import org.eclipse.cdt.debug.mi.core.output.CLIPTypeInfo;
import org.eclipse.cdt.debug.mi.core.output.MISrcAsm;
import org.eclipse.cdt.debug.mi.core.output.CLIWhatisInfo;


/**
 */
public class SourceManager extends Manager {

	GDBTypeParser gdbTypeParser;

	public SourceManager(Session session) {
		super(session, false);
		gdbTypeParser = new GDBTypeParser();
	}

	public void setSourcePaths(Target target, String[] dirs) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		MIEnvironmentDirectory dir = factory.createMIEnvironmentDirectory(true, dirs);
		try {
			mi.postCommand(dir);
			dir.getMIInfo();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public String[] getSourcePaths(Target target) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		MIGDBShowDirectories dir = factory.createMIGDBShowDirectories();
		try {
			mi.postCommand(dir);
			MIGDBShowDirectoriesInfo info = dir.getMIGDBShowDirectoriesInfo();
			return info.getDirectories();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public ICDIInstruction[] getInstructions(Target target, String filename, int linenum) throws CDIException {
		return getInstructions(target, filename, linenum, -1);
	}

	public ICDIInstruction[] getInstructions(Target target, String filename, int linenum, int lines) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		MIDataDisassemble dis = factory.createMIDataDisassemble(filename, linenum, lines, false);
		try {
			mi.postCommand(dis);
			MIDataDisassembleInfo info = dis.getMIDataDisassembleInfo();
			MIAsm[] asm = info.getMIAsms();
			Instruction[] instructions = new Instruction[asm.length];
			for (int i = 0; i < instructions.length; i++) {
				instructions[i] = new Instruction(target, asm[i]);
			}
			return instructions;
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public ICDIInstruction[] getInstructions(Target target, BigInteger start, BigInteger end) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		String hex = "0x"; //$NON-NLS-1$
		String sa = hex + start.toString(16);
		String ea = hex + end.toString(16);
		MIDataDisassemble dis = factory.createMIDataDisassemble(sa, ea, false);
		try {
			mi.postCommand(dis);
			MIDataDisassembleInfo info = dis.getMIDataDisassembleInfo();
			MIAsm[] asm = info.getMIAsms();
			Instruction[] instructions = new Instruction[asm.length];
			for (int i = 0; i < instructions.length; i++) {
				instructions[i] = new Instruction(target, asm[i]);
			}
			return instructions;
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public ICDIMixedInstruction[] getMixedInstructions(Target target, String filename, int linenum) throws CDIException {
		return getMixedInstructions(target, filename, linenum, -1);
	}

	public ICDIMixedInstruction[] getMixedInstructions(Target target, String filename, int linenum, int lines) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		MIDataDisassemble dis = factory.createMIDataDisassemble(filename, linenum, lines, true);
		try {
			mi.postCommand(dis);
			MIDataDisassembleInfo info = dis.getMIDataDisassembleInfo();
			MISrcAsm[] srcAsm = info.getMISrcAsms();
			ICDIMixedInstruction[] mixed = new ICDIMixedInstruction[srcAsm.length];
			for (int i = 0; i < mixed.length; i++) {
				mixed[i] = new MixedInstruction(target, srcAsm[i]);
			}
			return mixed;
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	public ICDIMixedInstruction[] getMixedInstructions(Target target, BigInteger start, BigInteger end) throws CDIException {
		MISession mi = target.getMISession();
		CommandFactory factory = mi.getCommandFactory();
		String hex = "0x"; //$NON-NLS-1$
		String sa = hex + start.toString(16);
		String ea = hex + end.toString(16);
		MIDataDisassemble dis = factory.createMIDataDisassemble(sa, ea, true);
		try {
			mi.postCommand(dis);
			MIDataDisassembleInfo info = dis.getMIDataDisassembleInfo();
			MISrcAsm[] srcAsm = info.getMISrcAsms();
			ICDIMixedInstruction[] mixed = new ICDIMixedInstruction[srcAsm.length];
			for (int i = 0; i < mixed.length; i++) {
				mixed[i] = new MixedInstruction(target, srcAsm[i]);
			}
			return mixed;
		} catch (MIException e) {
			throw new MI2CDIException(e);
		}
	}

	@Override
	public void update(Target target) throws CDIException {
	}

	public Type getType(Target target, String name) throws CDIException {
		if (name == null) {
			name = new String();
		}
		String typename = name.trim();

		// Parse the string.
		GDBType gdbType = gdbTypeParser.parse(typename);
		Type headType = null;
		Type type = null;

		// Convert the GDBType to an ICDIType.
		// So we go through the gdbType tree and reconstruct an ICDIType tree
		for (Type aType = null; gdbType != null; type = aType) {
			if (gdbType instanceof GDBDerivedType) {
				switch(gdbType.getType()) {
					case GDBType.ARRAY:
						int d = ((GDBDerivedType)gdbType).getDimension();
						aType = new ArrayType(target, gdbType.toString(), d);
					break;
					case GDBType.FUNCTION:
						aType = new FunctionType(target, gdbType.toString());
					break;
					case GDBType.POINTER:
						aType = new PointerType(target, gdbType.toString());
					break;
					case GDBType.REFERENCE:
						aType = new ReferenceType(target, gdbType.toString());
					break;
				}
				gdbType = ((GDBDerivedType)gdbType).getChild();
			} else {
				aType = toCDIType(target, gdbType.toString());
				gdbType = null;
			}
			if (type instanceof DerivedType) {
				((DerivedType)type).setComponentType(aType);
			}
			// Save the head to returning it.
			if (headType == null) {
				headType = aType;
			}
		}

		if (headType != null) {
			return headType;
		}
		throw new CDIException(CdiResources.getString("cdi.SourceManager.Unknown_type")); //$NON-NLS-1$
	}
	
	Type toCDIType(Target target, String name) throws CDIException {
		// Check the derived types and agregate types
		if (name == null) {
			name = new String();
		}
		String typename = name.trim();

		// Check the primitives.
		if (typename.equals("char")) { //$NON-NLS-1$
			return new CharType(target, typename);
		} else if (typename.equals("wchar_t")) { //$NON-NLS-1$
			return new WCharType(target, typename);
		} else if (typename.equals("short")) { //$NON-NLS-1$
			return new ShortType(target, typename);
		} else if (typename.equals("int")) { //$NON-NLS-1$
			return new IntType(target, typename);
		} else if (typename.equals("long")) { //$NON-NLS-1$
			return new LongType(target, typename);
		} else if (typename.equals("unsigned")) { //$NON-NLS-1$
			return new IntType(target, typename, true);
		} else if (typename.equals("signed")) { //$NON-NLS-1$
			return new IntType(target, typename);
		} else if (typename.equals("bool")) { //$NON-NLS-1$
			return new BoolType(target, typename);
		} else if (typename.equals("_Bool")) { //$NON-NLS-1$
			return new BoolType(target, typename);
		} else if (typename.equals("float")) { //$NON-NLS-1$
			return new FloatType(target, typename);
		} else if (typename.equals("double")) { //$NON-NLS-1$
			return new DoubleType(target, typename);
		} else if (typename.equals("void")) { //$NON-NLS-1$
			return new VoidType(target, typename);
		} else if (typename.equals("enum")) { //$NON-NLS-1$
			return new EnumType(target, typename);
		} else if (typename.equals("union")) { //$NON-NLS-1$
			return new StructType(target, typename);
		} else if (typename.equals("struct")) { //$NON-NLS-1$
			return new StructType(target, typename);
		} else if (typename.equals("class")) { //$NON-NLS-1$
			return new StructType(target, typename);
		}

		// GDB has some special types for int
		if (typename.equals("int8_t")) { //$NON-NLS-1$
			return new CharType(target, typename);
		} else if (typename.equals("int16_t")) { //$NON-NLS-1$
			return new ShortType(target, typename);
		} else if (typename.equals("int32_t")) { //$NON-NLS-1$
			return new IntType(target, typename);
		} else if (typename.equals("int64_t")) { //$NON-NLS-1$
			return new LongLongType(target, typename);
		} else if (typename.equals("int128_t")) { //$NON-NLS-1$
			return new IntType(target, typename); // ????
		}

		if (typename.equals("int8_t")) { //$NON-NLS-1$
			return new CharType(target, typename);
		} else if (typename.equals("uint8_t")) { //$NON-NLS-1$
			return new CharType(target, typename, true);
		} else if (typename.equals("int16_t")) { //$NON-NLS-1$
			return new ShortType(target, typename);
		} else if (typename.equals("uint16_t")) { //$NON-NLS-1$
			return new ShortType(target, typename, true);
		} else if (typename.equals("int32_t")) { //$NON-NLS-1$
			return new LongType(target, typename);
		} else if (typename.equals("uint32_t")) { //$NON-NLS-1$
			return new LongType(target, typename, true);
		} else if (typename.equals("int64_t")) { //$NON-NLS-1$
			return new LongLongType(target, typename);
		} else if (typename.equals("uint64_t")) { //$NON-NLS-1$
			return new LongLongType(target, typename, true);
		} else if (typename.equals("int128_t")) { //$NON-NLS-1$
			return new IntType(target, typename); // ????
		} else if (typename.equals("uint128_t")) { //$NON-NLS-1$
			return new IntType(target, typename, true); // ????			
		}

		StringTokenizer st = new StringTokenizer(typename);
		int count = st.countTokens();

		if (count == 2) {
			String first = st.nextToken();
			String second = st.nextToken();

			// ISOC allows permutations:
			// "signed int" and "int signed" are equivalent
			boolean isUnsigned =  (first.equals("unsigned") || second.equals("unsigned")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isSigned =    (first.equals("signed") || second.equals("signed")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isChar =      (first.equals("char") || second.equals("char")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isInt =       (first.equals("int") || second.equals("int")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isLong =      (first.equals("long") || second.equals("long")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isShort =     (first.equals("short") || second.equals("short")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isLongLong =  (first.equals("long") && second.equals("long")); //$NON-NLS-1$ //$NON-NLS-2$
			
			boolean isDouble =    (first.equals("double") || second.equals("double")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isFloat =     (first.equals("float") || second.equals("float")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isComplex =   (first.equals("complex") || second.equals("complex") || //$NON-NLS-1$ //$NON-NLS-2$
			                       first.equals("_Complex") || second.equals("_Complex")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isImaginery = (first.equals("_Imaginary") || second.equals("_Imaginary")); //$NON-NLS-1$ //$NON-NLS-2$

			boolean isStruct =     first.equals("struct"); //$NON-NLS-1$
			boolean isClass =      first.equals("class"); //$NON-NLS-1$
			boolean isUnion =      first.equals("union"); //$NON-NLS-1$
			boolean isEnum =       first.equals("enum"); //$NON-NLS-1$

			if (isChar && (isSigned || isUnsigned)) {
				return new CharType(target, typename, isUnsigned);
			} else if (isShort && (isInt || isSigned || isUnsigned)) {
				return new ShortType(target, typename, isUnsigned);
			} else if (isInt && (isSigned || isUnsigned)) {
				return new IntType(target, typename, isUnsigned);
			} else if (isLong && (isInt || isSigned || isUnsigned)) {
				return new LongType(target, typename, isUnsigned);
			} else if (isLongLong) {
				return new LongLongType(target, typename);
			} else if (isDouble && (isLong || isComplex || isImaginery)) {
				return new DoubleType(target, typename, isComplex, isImaginery, isLong);
			} else if (isFloat && (isComplex || isImaginery)) {
				return new FloatType(target, typename, isComplex, isImaginery);
			} else if (isStruct) {
				return new StructType(target, typename);
			} else if (isClass) {
				return new StructType(target, typename);
			} else if (isUnion) {
				return new StructType(target, typename);
			} else if (isEnum) {
				return new EnumType(target, typename);
			}
		} else if (count == 3) {
			// ISOC allows permutation. replace short by: long or short
			// "unsigned short int", "unsigned int short"
			// "short unsigned int". "short int unsigned"
			// "int unsinged short". "int short unsigned"
			//
			// "unsigned long long", "long long unsigned"
			// "signed long long", "long long signed"
			String first = st.nextToken();
			String second = st.nextToken();
			String third = st.nextToken();

			boolean isSigned =    (first.equals("signed") || second.equals("signed") || third.equals("signed")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean unSigned =    (first.equals("unsigned") || second.equals("unsigned") || third.equals("unsigned")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isInt =       (first.equals("int") || second.equals("int") || third.equals("int")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isLong =      (first.equals("long") || second.equals("long") || third.equals("long")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isShort =     (first.equals("short") || second.equals("short") || third.equals("short")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isLongLong =  (first.equals("long") && second.equals("long")) || //$NON-NLS-1$ //$NON-NLS-2$
			                       (second.equals("long") && third.equals("long")); //$NON-NLS-1$ //$NON-NLS-2$
			boolean isDouble =    (first.equals("double") || second.equals("double") || third.equals("double")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isComplex =   (first.equals("complex") || second.equals("complex") || third.equals("complex") || //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			                       first.equals("_Complex") || second.equals("_Complex") || third.equals("_Complex")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			boolean isImaginery = (first.equals("_Imaginary") || second.equals("_Imaginary") || third.equals("_Imaginary")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$


			if (isSigned == false && unSigned==false) {
				isSigned  = true; // otherwise long long int would not work
			}
			if (isShort && isInt && (isSigned || unSigned)) {
				return new ShortType(target, typename, unSigned);
			} else if (isLongLong && (isSigned || unSigned)) {
				return new LongLongType(target, typename, unSigned);
			} else if (isLong && isInt && (isSigned || unSigned)) {
				return new LongType(target, typename, unSigned);
			} else if (isDouble && isLong && (isComplex || isImaginery)) {
				return new DoubleType(target, typename, isComplex, isImaginery, isLong);
			}
		} else if (count == 4) {
			// ISOC allows permutation:
			// "unsigned long long int", "unsigned int long long"
			// "long long unsigned int". "long long int unsigned"
			// "int unsigned long long". "int long long unsigned"
			String first = st.nextToken();
			String second = st.nextToken();
			String third = st.nextToken();
			String fourth = st.nextToken();

			boolean unSigned = (first.equals("unsigned") || second.equals("unsigned") || third.equals("unsigned") || fourth.equals("unsigned")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			boolean isSigned = (first.equals("signed") || second.equals("signed") || third.equals("signed") || fourth.equals("signed")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			boolean isInt =    (first.equals("int") || second.equals("int") || third.equals("int") || fourth.equals("int")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			boolean isLongLong =   (first.equals("long") && second.equals("long")) //$NON-NLS-1$ //$NON-NLS-2$
				|| (second.equals("long") && third.equals("long")) //$NON-NLS-1$ //$NON-NLS-2$
				|| (third.equals("long") && fourth.equals("long")); //$NON-NLS-1$ //$NON-NLS-2$

			if (isLongLong && isInt && (isSigned || unSigned)) {
				return new LongLongType(target, typename, unSigned);
			}
		}
		throw new CDIException(CdiResources.getString("cdi.SourceManager.Unknown_type")); //$NON-NLS-1$
	}

	public String getDetailTypeNameFromVariable(StackFrame frame, String variable) throws CDIException {
		Target target = (Target)frame.getTarget();
		Thread currentThread = (Thread)target.getCurrentThread();
		StackFrame currentFrame = currentThread.getCurrentStackFrame();
		synchronized(target.getLock()) {
		try {
			target.setCurrentThread(frame.getThread(), false);
			((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
			return getDetailTypeName(target, variable);
		} finally {
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
		}
		}
	}
	public String getDetailTypeName(Target target, String typename) throws CDIException {
		try {
			MISession mi = target.getMISession();
			RxThread rxThread = mi.getRxThread();
			rxThread.setEnableConsole(false);
			CommandFactory factory = mi.getCommandFactory();
			CLIPType ptype = factory.createCLIPType(typename);
			mi.postCommand(ptype);
			CLIPTypeInfo info = ptype.getMIPtypeInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			return info.getType();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			MISession mi = target.getMISession();
			RxThread rxThread = mi.getRxThread();
			rxThread.setEnableConsole(true);

		}
	}

	public String getTypeNameFromVariable(StackFrame frame, String variable) throws CDIException {
		Target target = (Target)frame.getTarget();
		Thread currentThread = (Thread)target.getCurrentThread();
		StackFrame currentFrame = currentThread.getCurrentStackFrame();
		synchronized(target.getLock()) {
		try {
			target.setCurrentThread(frame.getThread(), false);
			((Thread)frame.getThread()).setCurrentStackFrame(frame, false);
			return getTypeName(target, variable);
		} finally {
				target.setCurrentThread(currentThread, false);
				currentThread.setCurrentStackFrame(currentFrame, false);
		}
		}
	}

	public String getTypeName(Target target, String variable) throws CDIException {
		MISession miSession = target.getMISession();
		try {
			RxThread rxThread = miSession.getRxThread();
			rxThread.setEnableConsole(false);
			CommandFactory factory = miSession.getCommandFactory();
			CLIWhatis whatis = factory.createCLIWhatis(variable);
			miSession.postCommand(whatis);
			CLIWhatisInfo info = whatis.getMIWhatisInfo();
			if (info == null) {
				throw new CDIException(CdiResources.getString("cdi.Common.No_answer")); //$NON-NLS-1$
			}
			return info.getType();
		} catch (MIException e) {
			throw new MI2CDIException(e);
		} finally {
			RxThread rxThread = miSession.getRxThread();
			rxThread.setEnableConsole(true);
		}
	}

}
