/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 flex.tools.debugger.cli;

import java.io.PrintWriter;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

import flash.localization.LocalizationManager;
import flash.swf.tools.Disassembler;
import flash.swf.types.ActionList;
import flash.tools.ActionLocation;
import flash.tools.debugger.Bootstrap;
import flash.tools.debugger.NotConnectedException;
import flash.tools.debugger.PlayerDebugException;
import flash.tools.debugger.Session;
import flash.tools.debugger.SourceFile;
import flash.tools.debugger.SuspendReason;
import flash.tools.debugger.SuspendedException;
import flash.tools.debugger.SwfInfo;
import flash.tools.debugger.Value;
import flash.tools.debugger.concrete.DMessage;
import flash.tools.debugger.concrete.DMessageCounter;
import flash.tools.debugger.concrete.DModule;
import flash.tools.debugger.concrete.DSuspendInfo;
import flash.tools.debugger.concrete.DSwfInfo;
import flash.tools.debugger.concrete.PlayerSession;
import flash.tools.debugger.concrete.PlayerSessionManager;
import flash.util.FieldFormat;

/**
 * Extensions class is a singleton that contains
 * every cli method that does not conform to the 
 * API.  Thus we can easily remove these features
 * from the cli if the implementation does not
 * support these calls.
 */
public class Extensions
{
	public final static String m_newline = System.getProperty("line.separator"); //$NON-NLS-1$

	public static void doShowStats(DebugCLI cli) throws IllegalStateException
	{
		/* we do some magic casting */
		Session session = cli.getSession();
		StringBuilder sb = new StringBuilder();
		try
		{
			PlayerSession p = (PlayerSession)session;
			DMessageCounter cnt = p.getMessageCounter();

			sb.append(getLocalizationManager().getLocalizedTextString("key16")); //$NON-NLS-1$
			sb.append(m_newline);
			for(int i=0; i<=DMessage.InSIZE; i++)
			{
				long amt = cnt.getInCount(i);
				if (amt > 0)
				{
					sb.append('\n');
					sb.append(DMessage.inTypeName(i));
					sb.append(" = "); //$NON-NLS-1$
					sb.append(amt);
				}
			}

			sb.append("\n\n"); //$NON-NLS-1$
			sb.append(getLocalizationManager().getLocalizedTextString("key17")); //$NON-NLS-1$
			sb.append("\n"); //$NON-NLS-1$
			for(int i=0; i<=DMessage.OutSIZE; i++)
			{
				long amt = cnt.getOutCount(i);
				if (amt > 0)
				{
					sb.append('\n');
					sb.append(DMessage.outTypeName(i));
					sb.append(" = "); //$NON-NLS-1$
					sb.append(amt);
				}
			}

			sb.append('\n');
			cli.out( sb.toString() );
		}
		catch(NullPointerException e)
		{
			throw new IllegalStateException();
		}
	}

	public static void doShowFuncs(DebugCLI cli)
	{
		StringBuilder sb = new StringBuilder();

		String arg = null;
		FileInfoCache fileInfo = cli.getFileCache();

		// we take an optional single arg which specifies a module
		try
		{
			if (cli.hasMoreTokens())
			{
				arg = cli.nextToken();
                int id = arg.equals(".") ? cli.propertyGet(DebugCLI.LIST_MODULE) : cli.parseFileArg(cli.getActiveIsolateId(), -1, arg); //$NON

				DModule m = (DModule)fileInfo.getFile(id, cli.getActiveIsolateId());
                m.lineMapping(sb);
			}
			else
			{
				SourceFile[] ar = fileInfo.getFileList();
				if (ar == null)
					cli.err(getLocalizationManager().getLocalizedTextString("key18")); //$NON-NLS-1$
				else
                {
                    for (int i = 0; i < ar.length; i++)
                    {
                        DModule m = (DModule)ar[i];
                        m.lineMapping(sb);
                    }
                }
			}

			cli.out(sb.toString());
		}
		catch(NullPointerException npe)
		{
			cli.err(getLocalizationManager().getLocalizedTextString("key19")); //$NON-NLS-1$
		}
		catch(ParseException pe)
		{
			cli.err(pe.getMessage());
		}
		catch(AmbiguousException ae)
		{
			cli.err(ae.getMessage());
		}
		catch(NoMatchException nme)
		{
			cli.err(nme.getMessage());
		}
	}

	/**
	 * Dump the content of internal variables
	 */
	public static void doShowProperties(DebugCLI cli)
	{
		StringBuilder sb = new StringBuilder();

		Session session = cli.getSession();
		for (String key: cli.propertyKeys())
		{
			int value = cli.propertyGet(key);
			sb.append(key);
			sb.append(" = "); //$NON-NLS-1$
			sb.append(value);
			sb.append('\n');
		}

		// session manager
		{
			PlayerSessionManager mgr = (PlayerSessionManager)Bootstrap.sessionManager();
			sb.append(getLocalizationManager().getLocalizedTextString("key21")); //$NON-NLS-1$
			sb.append('\n');
			for (String key: mgr.keySet())
			{
				Object value = mgr.getPreferenceAsObject(key);
				sb.append(key);
				sb.append(" = "); //$NON-NLS-1$
				sb.append(value);
				sb.append('\n');
			}
		}

		if (session != null)
		{
			PlayerSession psession = (PlayerSession)session;
			sb.append(getLocalizationManager().getLocalizedTextString("key22")); //$NON-NLS-1$
			sb.append('\n');
			for (String key: psession.keySet())
			{
				Object value = psession.getPreferenceAsObject(key);
				sb.append(key);
				sb.append(" = "); //$NON-NLS-1$
				sb.append(value);
				sb.append('\n');
			}
		}

		cli.out( sb.toString() );
	}

	/**
	 * Dump the break reason and offset
	 */
	public static void doShowBreak(DebugCLI cli) throws NotConnectedException
	{
		int isolateId = cli.getActiveIsolateId();
		cli.waitTilHalted(isolateId);
		try
		{
			Session session = cli.getSession();
			StringBuilder sb = new StringBuilder();
			if (session.getWorkerSession(isolateId).isSuspended())
			{
				sb.append(getLocalizationManager().getLocalizedTextString("stopped")); //$NON-NLS-1$
				sb.append(' ');
				appendBreakInfo(cli, sb, true, isolateId);
			}
			else
				sb.append(getLocalizationManager().getLocalizedTextString("key24")); //$NON-NLS-1$

			cli.out( sb.toString() );
		}
		catch(NullPointerException npe)
		{
			cli.err(getLocalizationManager().getLocalizedTextString("key25")); //$NON-NLS-1$
		}
	}

	// Extended low level break information
	public static void appendBreakInfo(DebugCLI cli, StringBuilder sb, boolean includeFault, int isolateId) throws NotConnectedException
	{
		Session session = cli.getSession();
		FileInfoCache fileInfo = cli.getFileCache();

		int reason = session.suspendReason();
		int offset = ((PlayerSession)session).getSuspendOffset();
		int index = ((PlayerSession)session).getSuspendActionIndex();

		SwfInfo info = null;
		try { info = fileInfo.getSwfs(isolateId)[index]; } catch(ArrayIndexOutOfBoundsException oobe) {}
		if (info != null)
		{
			Map<String, String> args = new HashMap<String, String>();
			args.put("swfName", FileInfoCache.nameOfSwf(info) ); //$NON-NLS-1$
			sb.append(getLocalizationManager().getLocalizedTextString("key35", args)); //$NON-NLS-1$
			sb.append(' ');
		}

		Map<String, String> args = new HashMap<String, String>();
		args.put("address", "0x" + FieldFormat.formatLongToHex(new StringBuilder(), offset, 8) + " (" + offset + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		sb.append(getLocalizationManager().getLocalizedTextString("atAddress", args)); //$NON-NLS-1$

		if (includeFault)
		{
			args = new HashMap<String, String>();
			StringBuilder reasonBuffer = new StringBuilder();
			cli.appendReason(reasonBuffer, reason);
			args.put("fault", reasonBuffer.toString() ); //$NON-NLS-1$
			sb.append(' ');
			sb.append(getLocalizationManager().getLocalizedTextString("haltedDueToFault", args)); //$NON-NLS-1$
		}
	}

	// Raw direct call to Player
	public static void doShowVariable(DebugCLI cli) throws PlayerDebugException
	{
		int isolateId = cli.getActiveIsolateId();
		cli.waitTilHalted(isolateId);
		try
		{
			// an integer followed by a variable name
			Session session = cli.getSession();
			long id = cli.nextLongToken();
			String name = (cli.hasMoreTokens()) ? cli.nextToken() : null;

			StringBuilder sb = new StringBuilder();
			sb.append(name);
			sb.append(" = "); //$NON-NLS-1$
			Value v = ((PlayerSession)session).getValue(id, name, isolateId);
			cli.m_exprCache.appendVariableValue(sb, v, isolateId);
			cli.out( sb.toString() );
		}
		catch(NullPointerException npe)
		{
			cli.err(getLocalizationManager().getLocalizedTextString("key26")); //$NON-NLS-1$
		}
	}

 	public static void doDisassemble(DebugCLI cli) throws PlayerDebugException
 	{
		/* currentXXX may NOT be invalid! */
		int currentModule = cli.propertyGet(DebugCLI.LIST_MODULE);
		int currentLine = cli.propertyGet(DebugCLI.LIST_LINE);
		int currentIsolate = cli.propertyGet(DebugCLI.LIST_WORKER);
 
		String arg1 = null;
		int module1 = currentModule;
		int line1 = currentLine;
 
		String arg2 = null;
		int line2 = currentLine;
 
 		boolean functionNamed = false;
		int numLines = 0;
 		try
 		{
			FileInfoCache fileInfo = cli.getFileCache();
			Session session = cli.getSession();
            int isolateId = cli.getActiveIsolateId();
			if (cli.hasMoreTokens())
 			{
				arg1 = cli.nextToken();
				if (arg1.equals("-")) //$NON-NLS-1$
				{
 					// move back one line
					line1 = line2 = line1 - 1;
				}
				else
				{
                    int wasFunc = 0;

                    FileLocation[] fileLocations = cli.parseLocationArg(currentModule, currentLine, arg1, false);

                    if (fileLocations.length == 1) {
                        module1 = fileLocations[0].getModule();
                        line2 = line1 = fileLocations[0].getLine();
                        functionNamed = (fileLocations[0].getWasFunc() != 0);
                    }

                    if (cli.hasMoreTokens()) {
                        arg2 = cli.nextToken();
                        line2 = cli.parseLineArg(module1, arg2);
                    }
				}
 			}
 			else
 			{
 				// since no parms test for valid location if none use players concept of where we stopped
 				if( fileInfo.getFile(currentModule, currentIsolate) == null)
 				{
 					//here we simply use the players concept of suspsend
 					DSuspendInfo info = ((PlayerSession)session).getSuspendInfoIsolate(isolateId);
 					int at = info.getOffset();
 					int which = info.getActionIndex();
 					int until = info.getNextOffset();
 					if (info.getReason() == SuspendReason.Unknown)
 						throw new SuspendedException();
 
 					SwfInfo swf = fileInfo.getSwfs(isolateId)[which];
 					outputAssembly(cli, (DSwfInfo)swf, at, until);
 					throw new AmbiguousException(getLocalizationManager().getLocalizedTextString("key27")); //$NON-NLS-1$
 				}
 			}			
 
 			/**
 			 * Check for a few error conditions, otherwise we'll write a listing!
 			 */
 			if (cli.hasMoreTokens())
 			{
 				cli.err(getLocalizationManager().getLocalizedTextString("key28")); //$NON-NLS-1$
 			}
 			else
 			{
 				SourceFile file = fileInfo.getFile(module1);
 				numLines = file.getLineCount();
 
 				// pressing return is ok, otherwise throw the exception
 				if (line1 > numLines && arg1 != null)
 					throw new IndexOutOfBoundsException();
 
 				/* if no arg2 then user list a single line */
 				if (arg2 == null)
 					line2 = line1;
 
 				/* adjust our range of lines to ensure we conform */
 				if (line1 < 1)
 				{
 					/* shrink line 1, grow line2 */
 					line2 += -(line1 - 1);
 					line1 = 1;
 				}
 
 				if (line2 > numLines)
 					line2 = numLines;
 
				//			    System.out.println("1="+module1+":"+line1+",2="+module2+":"+line2+",num="+numLines+",half="+half);
 
 				/* nothing to display */
 				if (line1 > line2)
 					throw new IndexOutOfBoundsException();
 
 				/* now dump the mixed source / assembly */
 				// now lets find which swf this in 
 				DSwfInfo swf = (DSwfInfo)fileInfo.swfForFile(file, cli.getActiveIsolateId());
 				ActionLocation lStart = null;
 				ActionLocation lEnd = null;
 
 				if (swf == null)
				{
					Map<String, String> args = new HashMap<String, String>();
					args.put("arg3", file.getName()); //$NON-NLS-1$
 					cli.err(getLocalizationManager().getLocalizedTextString("key29", args)); //$NON-NLS-1$
				}
 				else if (functionNamed)
 				{
 					// if we name a function just dump the whole thing without source.
 					int offset = file.getOffsetForLine(line1);
 					lStart = swf.locate(offset);
 					if (lStart.function == null)
 						cli.err(getLocalizationManager().getLocalizedTextString("key30")); //$NON-NLS-1$
 					else
 					{
 						// create a psudeo action list from which to disasemble the function
 						ActionList al = new ActionList(true);
 						al.setActionOffset(0, lStart.function);
 						lStart.actions = al;
 						lStart.at = 0;
 						lEnd = new ActionLocation();
 						lEnd.actions = al;
 						lEnd.at = 0;
 						outputAssembly(cli, swf, lStart, lEnd);
 					}
 				}
 				else
 				{
 					ActionLocation lastEnd = null;
 					for(int i=line1; i<=line2; i++)
 					{
 						int offset = file.getOffsetForLine(i);
 
 						// locate the action list associated with this of the swf
 						if (offset != 0)
 						{
 							// get the starting point and try to locate a nice ending
 							lStart = swf.locate(offset);
 							lEnd = swf.locateSourceLineEnd(lStart);
 
 							// now see if we skipped some assembly between source lines
 							if (lastEnd != null)
 							{
 								lastEnd.at++;  // point our pseudo start to the next action
 
 								// new actions list so attempt to find the end of source in the old actions list
 								if (lastEnd.actions != lStart.actions && lastEnd.actions.size() != lastEnd.at)
 								{
 									String atString = Integer.toHexString(lastEnd.actions.getOffset(lastEnd.at));
									Map<String, String> args = new HashMap<String, String>();
									args.put("arg4", atString); //$NON-NLS-1$
 									cli.out(getLocalizationManager().getLocalizedTextString("key31", args)); //$NON-NLS-1$
 
  									// we are missing some of the dissassembly, so back up a bit and dump it out
 									ActionLocation gapEnd = swf.locateSourceLineEnd(lastEnd);
 									outputAssembly(cli, swf, lastEnd, gapEnd);
 								}
 								else if (lastEnd.at < lStart.at)
 								{
 									// same action list but we skipped some instructions 
 									ActionLocation gapEnd = new ActionLocation(lStart);
 									gapEnd.at--;
 									outputAssembly(cli, swf, lastEnd, gapEnd);
 								}
 							}
 							lastEnd = lEnd;
 						}
 
 						// dump source
 						cli.outputSource(module1, i, file.getLine(i));
 						
 						// obtain the offset, locate it in the swf
 						if (offset != 0)
 							outputAssembly(cli, swf, lStart, lEnd);
 					}
 
 					/* save away valid context */
 					cli.propertyPut(DebugCLI.LIST_MODULE, module1);
 					cli.propertyPut(DebugCLI.LIST_LINE, line2 + 1);  // add one
 					cli.m_repeatLine = "disassemble";   /* allow repeated listing by typing CR */ //$NON-NLS-1$
 				}
 			}
 		}
		catch(IndexOutOfBoundsException iob)
 		{
 			String name = "#"+module1; //$NON-NLS-1$
			Map<String, String> args = new HashMap<String, String>();
			args.put("arg5", Integer.toString(line1)); //$NON-NLS-1$
			args.put("arg6", name); //$NON-NLS-1$
			args.put("arg7", Integer.toString(numLines)); //$NON-NLS-1$
 			cli.err(getLocalizationManager().getLocalizedTextString("key32", args)); //$NON-NLS-1$
 		}
 		catch(AmbiguousException ae)
 		{
 			cli.err(ae.getMessage());
 		}
 		catch(NullPointerException npe)
 		{
 			cli.err(getLocalizationManager().getLocalizedTextString("key33")); //$NON-NLS-1$
 		}
 		catch(ParseException pe)
 		{
 			cli.err(pe.getMessage());
 		}
		catch(NoMatchException nme)
		{
			cli.err(nme.getMessage());
		}
 		catch(SuspendedException se)
 		{
			cli.err(getLocalizationManager().getLocalizedTextString("key34")); //$NON-NLS-1$
 		}
 	}
 
 	private static LocalizationManager getLocalizationManager()
	{
 		return DebugCLI.getLocalizationManager();
	}

	/**
 	 * Disassemble part of the swf to the output 
 	 */
 	public static ActionLocation outputAssembly(DebugCLI cli, DSwfInfo swf, int start, int end)
 	{
 		// first we need to locate the action list associated with this
 		// portion of the swf
 		ActionLocation lStart = swf.locate(start);
 		ActionLocation lEnd = (end > -1) ? swf.locate(end) : swf.locateSourceLineEnd(lStart);
 
 		return outputAssembly(cli, swf, lStart, lEnd);
 	}
 
 	public static ActionLocation outputAssembly(DebugCLI cli, SwfInfo info, ActionLocation lStart, ActionLocation lEnd)
 	{
 		// now make sure our actions lists are the same (i.e we haven't spanned past one tag)
 		if (lStart.actions != lEnd.actions)
 			lEnd.at = lStart.actions.size()-1;
 		
 		Disassembler.disassemble(lStart.actions, lStart.pool, lStart.at, lEnd.at, new PrintWriter(cli.getOut()));
 		return lEnd;
 	}
}
