using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Server;
using Server.Diagnostics;

namespace Server.Commands
{
	public class Profiling
	{
		public static void Initialize()
		{
			CommandSystem.Register( "DumpTimers", AccessLevel.Administrator, new CommandEventHandler( DumpTimers_OnCommand ) );
			CommandSystem.Register( "CountObjects", AccessLevel.Administrator, new CommandEventHandler( CountObjects_OnCommand ) );
			CommandSystem.Register( "ProfileWorld", AccessLevel.Administrator, new CommandEventHandler( ProfileWorld_OnCommand ) );
			CommandSystem.Register( "TraceInternal", AccessLevel.Administrator, new CommandEventHandler( TraceInternal_OnCommand ) );
			CommandSystem.Register( "TraceExpanded", AccessLevel.Administrator, new CommandEventHandler( TraceExpanded_OnCommand ) );
			CommandSystem.Register( "WriteProfiles", AccessLevel.Administrator, new CommandEventHandler( WriteProfiles_OnCommand ) );
			CommandSystem.Register( "SetProfiles", AccessLevel.Administrator, new CommandEventHandler( SetProfiles_OnCommand ) );
		}

		[Usage( "WriteProfiles" )]
		[Description( "Generates a log files containing performance diagnostic information." )]
		public static void WriteProfiles_OnCommand( CommandEventArgs e )
		{
			try
			{
				using ( StreamWriter sw = new StreamWriter( "profiles.log", true ) )
				{
					sw.WriteLine( "# Dump on {0:f}", DateTime.Now );
					sw.WriteLine( "# Core profiling for " + Core.ProfileTime );

					sw.WriteLine( "# Packet send" );
					BaseProfile.WriteAll( sw, PacketSendProfile.Profiles );
					sw.WriteLine();

					sw.WriteLine( "# Packet receive" );
					BaseProfile.WriteAll( sw, PacketReceiveProfile.Profiles );
					sw.WriteLine();

					sw.WriteLine( "# Timer" );
					BaseProfile.WriteAll( sw, TimerProfile.Profiles );
					sw.WriteLine();

					sw.WriteLine( "# Gump response" );
					BaseProfile.WriteAll( sw, GumpProfile.Profiles );
					sw.WriteLine();

					sw.WriteLine( "# Target response" );
					BaseProfile.WriteAll( sw, TargetProfile.Profiles );
					sw.WriteLine();
				}
			}
			catch
			{
			}
		}

		[Usage( "SetProfiles [true | false]" )]
		[Description( "Enables, disables, or toggles the state of core packet and timer profiling." )]
		public static void SetProfiles_OnCommand( CommandEventArgs e )
		{
			if ( e.Length == 1 )
				Core.Profiling = e.GetBoolean( 0 );
			else
				Core.Profiling = !Core.Profiling;

			e.Mobile.SendMessage( "Profiling has been {0}.", Core.Profiling ? "enabled" : "disabled" );
		}

		[Usage( "DumpTimers" )]
		[Description( "Generates a log file of all currently executing timers. Used for tracing timer leaks." )]
		public static void DumpTimers_OnCommand( CommandEventArgs e )
		{
			try
			{
				using ( StreamWriter sw = new StreamWriter( "timerdump.log", true ) )
					Timer.DumpInfo( sw );
			}
			catch
			{
			}
		}

		private class CountSorter : IComparer
		{
			public int Compare( object x, object y )
			{
				DictionaryEntry a = (DictionaryEntry)x;
				DictionaryEntry b = (DictionaryEntry)y;

				int aCount = GetCount( a.Value );
				int bCount = GetCount( b.Value );

				int v = -aCount.CompareTo( bCount );

				if ( v == 0 )
				{
					Type aType = (Type)a.Key;
					Type bType = (Type)b.Key;

					v = aType.FullName.CompareTo( bType.FullName );
				}

				return v;
			}

			private int GetCount( object obj )
			{
				if ( obj is int )
					return (int) obj;

				if ( obj is int[] )
				{
					int[] list = (int[]) obj;

					int total = 0;

					for ( int i = 0; i < list.Length; ++i )
						total += list[i];

					return total;
				}

				return 0;
			}
		}

		[Usage( "CountObjects" )]
		[Description( "Generates a log file detailing all item and mobile types in the world." )]
		public static void CountObjects_OnCommand( CommandEventArgs e )
		{
			using ( StreamWriter op = new StreamWriter( "objects.log" ) )
			{
				Hashtable table = new Hashtable();

				foreach ( Item item in World.Items.Values )
				{
					Type type = item.GetType();

					object o = (object)table[type];

					if ( o == null )
						table[type] = 1;
					else
						table[type] = 1 + (int)o;
				}

				ArrayList items = new ArrayList( table );

				table.Clear();

				foreach ( Mobile m in World.Mobiles.Values )
				{
					Type type = m.GetType();

					object o = (object)table[type];

					if ( o == null )
						table[type] = 1;
					else
						table[type] = 1 + (int)o;
				}

				ArrayList mobiles = new ArrayList( table );

				items.Sort( new CountSorter() );
				mobiles.Sort( new CountSorter() );

				op.WriteLine( "# Object count table generated on {0}", DateTime.Now );
				op.WriteLine();
				op.WriteLine();

				op.WriteLine( "# Items:" );

				foreach ( DictionaryEntry de in items )
					op.WriteLine( "{0}\t{1:F2}%\t{2}", de.Value, (100 * (int)de.Value) / (double)World.Items.Count, de.Key );

				op.WriteLine();
				op.WriteLine();

				op.WriteLine( "#Mobiles:" );

				foreach ( DictionaryEntry de in mobiles )
					op.WriteLine( "{0}\t{1:F2}%\t{2}", de.Value, (100 * (int)de.Value) / (double)World.Mobiles.Count, de.Key );
			}

			e.Mobile.SendMessage( "Object table has been generated. See the file : <runuo root>/objects.log" );
		}

		[Usage( "TraceExpanded" )]
		[Description( "Generates a log file describing all items using expanded memory." )]
		public static void TraceExpanded_OnCommand( CommandEventArgs e )
		{
			Hashtable typeTable = new Hashtable();

			foreach ( Item item in World.Items.Values )
			{
				ExpandFlag flags = item.GetExpandFlags();

				if ( ( flags & ~(ExpandFlag.TempFlag | ExpandFlag.SaveFlag) ) == 0 )
					continue;

				Type itemType = item.GetType();

				do
				{
					int[] countTable = typeTable[itemType] as int[];

					if ( countTable == null )
						typeTable[itemType] = countTable = new int[8];

					if ( ( flags & ExpandFlag.Name ) != 0 )
						++countTable[0];

					if ( ( flags & ExpandFlag.Items ) != 0 )
						++countTable[1];

					if ( ( flags & ExpandFlag.Bounce ) != 0 )
						++countTable[2];

					if ( ( flags & ExpandFlag.Holder ) != 0 )
						++countTable[3];

					if ( ( flags & ExpandFlag.Blessed ) != 0 )
						++countTable[4];

					/*if ( ( flags & ExpandFlag.TempFlag ) != 0 )
						++countTable[5];

					if ( ( flags & ExpandFlag.SaveFlag ) != 0 )
						++countTable[6];*/

					if ( ( flags & ExpandFlag.Weight ) != 0 )
						++countTable[7];

					itemType = itemType.BaseType;
				} while ( itemType != typeof( object ) );
			}

			try
			{
				using ( StreamWriter op = new StreamWriter( "expandedItems.log", true ) )
				{
					string[] names = new string[]
					{
						"Name",
						"Items",
						"Bounce",
						"Holder",
						"Blessed",
						"TempFlag",
						"SaveFlag",
						"Weight"
					};

					ArrayList list = new ArrayList( typeTable );

					list.Sort( new CountSorter() );

					foreach ( DictionaryEntry de in list )
					{
						Type itemType = de.Key as Type;
						int[] countTable = de.Value as int[];

						op.WriteLine( "# {0}", itemType.FullName );

						for ( int i = 0; i < countTable.Length; ++i )
						{
							if ( countTable[i] > 0 )
								op.WriteLine( "{0}\t{1:N0}", names[i], countTable[i] );
						}

						op.WriteLine();
					}
				}
			}
			catch
			{
			}
		}

		[Usage( "TraceInternal" )]
		[Description( "Generates a log file describing all items in the 'internal' map." )]
		public static void TraceInternal_OnCommand( CommandEventArgs e )
		{
			int totalCount = 0;
			Hashtable table = new Hashtable();

			foreach ( Item item in World.Items.Values )
			{
				if ( item.Parent != null || item.Map != Map.Internal )
					continue;

				++totalCount;

				Type type = item.GetType();
				int[] parms = (int[])table[type];

				if ( parms == null )
					table[type] = parms = new int[]{ 0, 0 };

				parms[0]++;
				parms[1] += item.Amount;
			}

			using ( StreamWriter op = new StreamWriter( "internal.log" ) )
			{
				op.WriteLine( "# {0} items found", totalCount );
				op.WriteLine( "# {0} different types", table.Count );
				op.WriteLine();
				op.WriteLine();
				op.WriteLine( "Type\t\tCount\t\tAmount\t\tAvg. Amount" );

				foreach ( DictionaryEntry de in table )
				{
					Type type = (Type)de.Key;
					int[] parms = (int[])de.Value;

					op.WriteLine( "{0}\t\t{1}\t\t{2}\t\t{3:F2}", type.Name, parms[0], parms[1], (double)parms[1] / parms[0] );
				}
			}
		}

		[Usage( "ProfileWorld" )]
		[Description( "Prints the amount of data serialized for every object type in your world file." )]
		public static void ProfileWorld_OnCommand( CommandEventArgs e )
		{
			ProfileWorld( "items", "worldprofile_items.log" );
			ProfileWorld( "mobiles", "worldprofile_mobiles.log" );
		}

		public static void ProfileWorld( string type, string opFile )
		{
			try
			{
				ArrayList types = new ArrayList();

				using ( BinaryReader bin = new BinaryReader( new FileStream( String.Format( "Saves/{0}/{0}.tdb", type ), FileMode.Open, FileAccess.Read, FileShare.Read ) ) )
				{
					int count = bin.ReadInt32();

					for ( int i = 0; i < count; ++i )
						types.Add( ScriptCompiler.FindTypeByFullName( bin.ReadString() ) );
				}

				long total = 0;

				Hashtable table = new Hashtable();

				using ( BinaryReader bin = new BinaryReader( new FileStream( String.Format( "Saves/{0}/{0}.idx", type ), FileMode.Open, FileAccess.Read, FileShare.Read ) ) )
				{
					int count = bin.ReadInt32();

					for ( int i = 0; i < count; ++i )
					{
						int typeID = bin.ReadInt32();
						int serial = bin.ReadInt32();
						long pos = bin.ReadInt64();
						int length = bin.ReadInt32();
						Type objType = (Type)types[typeID];

						while ( objType != null && objType != typeof( object ) )
						{
							object obj = table[objType];

							if ( obj == null )
								table[objType] = length;
							else
								table[objType] = length + (int)obj;

							objType = objType.BaseType;
							total += length;
						}
					}
				}

				ArrayList list = new ArrayList( table );

				list.Sort( new CountSorter() );

				using ( StreamWriter op = new StreamWriter( opFile ) )
				{
					op.WriteLine( "# Profile of world {0}", type );
					op.WriteLine( "# Generated on {0}", DateTime.Now );
					op.WriteLine();
					op.WriteLine();

					foreach ( DictionaryEntry de in list )
						op.WriteLine( "{0}\t{1:F2}%\t{2}", de.Value, (100 * (int)de.Value) / (double)total, de.Key );
				}
			}
			catch
			{
			}
		}
	}
}