using System;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Server;
using Server.Gumps;
using Server.Network;
using Server.Prompts;

namespace Server.Misc
{
	public class ShardPoller : Item
	{
		private string m_Title;

		private ShardPollOption[] m_Options;
		private IPAddress[] m_Addresses;

		private TimeSpan m_Duration;
		private DateTime m_StartTime;

		private bool m_Active;

		public ShardPollOption[] Options
		{
			get{ return m_Options; }
			set{ m_Options = value; }
		}

		public IPAddress[] Addresses
		{
			get{ return m_Addresses; }
			set{ m_Addresses = value; }
		}

		[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
		public string Title
		{
			get{ return m_Title; }
			set{ m_Title = ShardPollPrompt.UrlToHref( value ); }
		}

		[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
		public TimeSpan Duration
		{
			get{ return m_Duration; }
			set{ m_Duration = value; }
		}

		[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
		public DateTime StartTime
		{
			get{ return m_StartTime; }
			set{ m_StartTime = value; }
		}

		[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
		public TimeSpan TimeRemaining
		{
			get
			{
				if ( m_StartTime == DateTime.MinValue || !m_Active )
					return TimeSpan.Zero;

				try
				{
					TimeSpan ts = (m_StartTime + m_Duration) - DateTime.Now;

					if ( ts < TimeSpan.Zero )
						return TimeSpan.Zero;

					return ts;
				}
				catch
				{
					return TimeSpan.Zero;
				}
			}
		}

		[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
		public bool Active
		{
			get{ return m_Active; }
			set
			{
				if ( m_Active == value )
					return;

				m_Active = value;

				if ( m_Active )
				{
					m_StartTime = DateTime.Now;
					m_ActivePollers.Add( this );
				}
				else
				{
					m_ActivePollers.Remove( this );
				}
			}
		}

		public bool HasAlreadyVoted( NetState ns )
		{
			for ( int i = 0; i < m_Options.Length; ++i )
			{
				if ( m_Options[i].HasAlreadyVoted( ns ) )
					return true;
			}

			return false;
		}

		public void AddVote( NetState ns, ShardPollOption option )
		{
			option.AddVote( ns );
		}

		public void RemoveOption( ShardPollOption option )
		{
			int index = Array.IndexOf( m_Options, option );

			if ( index < 0 )
				return;

			ShardPollOption[] old = m_Options;
			m_Options = new ShardPollOption[old.Length - 1];

			for ( int i = 0; i < index; ++i )
				m_Options[i] = old[i];

			for ( int i = index; i < m_Options.Length; ++i )
				m_Options[i] = old[i + 1];
		}

		public void AddOption( ShardPollOption option )
		{
			ShardPollOption[] old = m_Options;
			m_Options = new ShardPollOption[old.Length + 1];

			for ( int i = 0; i < old.Length; ++i )
				m_Options[i] = old[i];

			m_Options[old.Length] = option;
		}

		public override string DefaultName
		{
			get { return "shard poller"; }
		}

		[Constructable( AccessLevel.Administrator )]
		public ShardPoller() : base( 0x1047 )
		{
			m_Duration = TimeSpan.FromHours( 24.0 );
			m_Options = new ShardPollOption[0];
			m_Addresses = new IPAddress[0];

			Movable = false;
		}

		public static void Initialize()
		{
			EventSink.Login += new LoginEventHandler( EventSink_Login );
		}

		private static List<ShardPoller> m_ActivePollers = new List<ShardPoller>();

		private static void EventSink_Login( LoginEventArgs e )
		{
			if ( m_ActivePollers.Count == 0 )
				return;

			Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), new TimerStateCallback( EventSink_Login_Callback ), e.Mobile );
		}

		private static void EventSink_Login_Callback( object state )
		{
			Mobile from = (Mobile)state;
			NetState ns = from.NetState;

			if ( ns == null )
				return;

			ShardPollGump spg = null;

			for ( int i = 0; i < m_ActivePollers.Count; ++i )
			{
				ShardPoller poller = m_ActivePollers[i];

				if ( poller.Deleted || !poller.Active )
					continue;

				if ( poller.TimeRemaining > TimeSpan.Zero )
				{
					if ( poller.HasAlreadyVoted( ns ) )
						continue;

					if ( spg == null )
					{
						spg = new ShardPollGump( from, poller, false, null );
						from.SendGump( spg );
					}
					else
					{
						spg.QueuePoll( poller );
					}
				}
				else
				{
					poller.Active = false;
				}
			}
		}

		public void SendQueuedPoll_Callback( object state )
		{
			object[] states = (object[])state;
			Mobile from = (Mobile)states[0];
			Queue<ShardPoller> queue = (Queue<ShardPoller>)states[1];

			from.SendGump( new ShardPollGump( from, this, false, queue ) );
		}

		public override void OnDoubleClick( Mobile from )
		{
			if ( from.AccessLevel >= AccessLevel.Administrator )
				from.SendGump( new ShardPollGump( from, this, true, null ) );
		}

		public ShardPoller( Serial serial ) : base( serial )
		{
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 0 ); // version

			writer.Write( m_Title );
			writer.Write( m_Duration );
			writer.Write( m_StartTime );
			writer.Write( m_Active );

			writer.Write( m_Options.Length );

			for ( int i = 0; i < m_Options.Length; ++i )
				m_Options[i].Serialize( writer );
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();

			switch ( version )
			{
				case 0:
				{
					m_Title = reader.ReadString();
					m_Duration = reader.ReadTimeSpan();
					m_StartTime = reader.ReadDateTime();
					m_Active = reader.ReadBool();

					m_Options = new ShardPollOption[reader.ReadInt()];

					for ( int i = 0; i < m_Options.Length; ++i )
						m_Options[i] = new ShardPollOption( reader );

					if ( m_Active )
						m_ActivePollers.Add( this );

					break;
				}
			}
		}

		public override void OnDelete()
		{
			base.OnDelete();

			Active = false;
		}
	}

	public class ShardPollOption
	{
		private string m_Title;
		private int m_LineBreaks;
		private IPAddress[] m_Voters;

		public string Title{ get{ return m_Title; } set{ m_Title = value; m_LineBreaks = GetBreaks( m_Title ); } }
		public int LineBreaks{ get{ return m_LineBreaks; } }

		public int Votes{ get{ return m_Voters.Length; } }
		public IPAddress[] Voters{ get{ return m_Voters; } set{ m_Voters = value; } }

		public ShardPollOption( string title )
		{
			m_Title = title;
			m_LineBreaks = GetBreaks( m_Title );
			m_Voters = new IPAddress[0];
		}

		public bool HasAlreadyVoted( NetState ns )
		{
			if ( ns == null )
				return false;

			IPAddress ipAddress = ns.Address;

			for ( int i = 0; i < m_Voters.Length; ++i )
			{
				if ( Utility.IPMatchClassC( m_Voters[i], ipAddress ) )
					return true;
			}

			return false;
		}

		public void AddVote( NetState ns )
		{
			if ( ns == null )
				return;

			IPAddress[] old = m_Voters;
			m_Voters = new IPAddress[old.Length + 1];

			for ( int i = 0; i < old.Length; ++i )
				m_Voters[i] = old[i];

			m_Voters[old.Length] = ns.Address;
		}

		public int ComputeHeight()
		{
			int height = m_LineBreaks * 18;

			if ( height > 30 )
				return height;

			return 30;
		}

		public int GetBreaks( string title )
		{
			if ( title == null )
				return 1;

			int count = 0;
			int index = -1;

			do
			{
				++count;
				index = title.IndexOf( "<br>", index + 1 );
			} while ( index >= 0 );

			return count;
		}

		public ShardPollOption( GenericReader reader )
		{
			int version = reader.ReadInt();

			switch ( version )
			{
				case 0:
				{
					m_Title = reader.ReadString();
					m_LineBreaks = GetBreaks( m_Title );

					m_Voters = new IPAddress[reader.ReadInt()];

					for ( int i = 0; i < m_Voters.Length; ++i )
						m_Voters[i] = Utility.Intern( reader.ReadIPAddress() );

					break;
				}
			}
		}

		public void Serialize( GenericWriter writer )
		{
			writer.Write( (int) 0 ); // version

			writer.Write( m_Title );

			writer.Write( m_Voters.Length );

			for ( int i = 0; i < m_Voters.Length; ++i )
				writer.Write( m_Voters[i] );
		}
	}

	public class ShardPollGump : Gump
	{
		private Mobile m_From;
		private ShardPoller m_Poller;
		private bool m_Editing;
		private Queue<ShardPoller> m_Polls;

		public bool Editing{ get{ return m_Editing; } }

		public void QueuePoll( ShardPoller poller )
		{
			if ( m_Polls == null )
				m_Polls = new Queue<ShardPoller>( 4 );

			m_Polls.Enqueue( poller );
		}

		public string Center( string text )
		{
			return String.Format( "<CENTER>{0}</CENTER>", text );
		}

		public string Color( string text, int color )
		{
			return String.Format( "<BASEFONT COLOR=#{0:X6}>{1}</BASEFONT>", color, text );
		}

		private const int LabelColor32 = 0xFFFFFF;

		public ShardPollGump( Mobile from, ShardPoller poller, bool editing, Queue<ShardPoller> polls ) : base( 50, 50 )
		{
			m_From = from;
			m_Poller = poller;
			m_Editing = editing;
			m_Polls = polls;

			Closable = false;

			AddPage( 0 );

			int totalVotes = 0;
			int totalOptionHeight = 0;

			for ( int i = 0; i < poller.Options.Length; ++i )
			{
				totalVotes += poller.Options[i].Votes;
				totalOptionHeight += poller.Options[i].ComputeHeight() + 5;
			}

			bool isViewingResults = editing && poller.Active;
			bool isCompleted = totalVotes > 0 && !poller.Active;

			if ( editing && !isViewingResults )
				totalOptionHeight += 35;

			int height = 115 + totalOptionHeight;

			AddBackground( 1, 1, 398, height - 2, 3600 );
			AddAlphaRegion( 16, 15, 369, height - 31 );

			AddItem( 308, 30, 0x1E5E );

			string title;

			if ( editing )
				title = ( isCompleted ? "Poll Completed" : "Poll Editor" );
			else
				title = "Shard Poll";

			AddHtml( 22, 22, 294, 20, Color( Center( title ), LabelColor32 ), false, false );

			if ( editing )
			{
				AddHtml( 22, 22, 294, 20, Color( String.Format( "{0} total", totalVotes ), LabelColor32 ), false, false );
				AddButton( 287, 23, 0x2622, 0x2623, 2, GumpButtonType.Reply, 0 );
			}

			AddHtml( 22, 50, 294, 40, Color( poller.Title, 0x99CC66 ), false, false );

			AddImageTiled( 32, 88, 264, 1, 9107 );
			AddImageTiled( 42, 90, 264, 1, 9157 );

			int y = 100;

			for ( int i = 0; i < poller.Options.Length; ++i )
			{
				ShardPollOption option = poller.Options[i];
				string text = option.Title;

				if ( editing && totalVotes > 0 )
				{
					double perc = option.Votes / (double)totalVotes;

					text = String.Format( "[{1}: {2}%] {0}", text, option.Votes, (int)(perc*100) );
				}

				int optHeight = option.ComputeHeight();

				y += optHeight/2;

				if ( isViewingResults )
					AddImage( 24, y - 15, 0x25FE );
				else
					AddRadio( 24, y - 15, 0x25F9, 0x25FC, false, 1 + i );

				AddHtml( 60, y - (9 * option.LineBreaks), 250, 18 * option.LineBreaks, Color( text, LabelColor32 ), false, false );

				y += optHeight/2;
				y += 5;
			}

			if ( editing && !isViewingResults )
			{
				AddRadio( 24, y + 15 - 15, 0x25F9, 0x25FC, false, 1 + poller.Options.Length );
				AddHtml( 60, y + 15 - 9, 250, 18, Color( "Create new option.", 0x99CC66 ), false, false );
			}

			AddButton( 314, height - 73, 247, 248, 1, GumpButtonType.Reply, 0 );
			AddButton( 314, height - 47, 242, 241, 0, GumpButtonType.Reply, 0 );
		}

		public override void OnResponse( NetState sender, RelayInfo info )
		{
			if ( m_Polls != null && m_Polls.Count > 0 )
			{
				ShardPoller poller = m_Polls.Dequeue();

				if ( poller != null )
					Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), new TimerStateCallback( poller.SendQueuedPoll_Callback ), new object[]{ m_From, m_Polls } );
			}

			if ( info.ButtonID == 1 )
			{
				int[] switches = info.Switches;

				if ( switches.Length == 0 )
					return;

				int switched = switches[0] - 1;
				ShardPollOption opt = null;

				if ( switched >= 0 && switched < m_Poller.Options.Length )
					opt = m_Poller.Options[switched];

				if ( opt == null && !m_Editing )
					return;

				if ( m_Editing )
				{
					if ( !m_Poller.Active )
					{
						m_From.SendMessage( "Enter a title for the option. Escape to cancel.{0}", opt == null ? "" : " Use \"DEL\" to delete." );
						m_From.Prompt = new ShardPollPrompt( m_Poller, opt );
					}
					else
					{
						m_From.SendMessage( "You may not edit an active poll. Deactivate it first." );
						m_From.SendGump( new ShardPollGump( m_From, m_Poller, m_Editing, m_Polls ) );
					}
				}
				else
				{
					if ( !m_Poller.Active )
						m_From.SendMessage( "The poll has been deactivated." );
					else if ( m_Poller.HasAlreadyVoted( sender ) )
						m_From.SendMessage( "You have already voted on this poll." );
					else
						m_Poller.AddVote( sender, opt );
				}
			}
			else if ( info.ButtonID == 2 && m_Editing )
			{
				m_From.SendGump( new ShardPollGump( m_From, m_Poller, m_Editing, m_Polls ) );
				m_From.SendGump( new PropertiesGump( m_From, m_Poller ) );
			}
		}
	}

	public class ShardPollPrompt : Prompt
	{
		private ShardPoller m_Poller;
		private ShardPollOption m_Option;

		public ShardPollPrompt( ShardPoller poller, ShardPollOption opt )
		{
			m_Poller = poller;
			m_Option = opt;
		}

		public override void OnCancel( Mobile from )
		{
			from.SendGump( new ShardPollGump( from, m_Poller, true, null ) );
		}

		private static Regex m_UrlRegex = new Regex( @"\[url(?:=(.*?))?\](.*?)\[/url\]", RegexOptions.IgnoreCase | RegexOptions.Compiled );

		private static string UrlRegex_Match( Match m )
		{
			if ( m.Groups[1].Success )
			{
				if ( m.Groups[2].Success )
					return String.Format( "<a href=\"{0}\">{1}</a>", m.Groups[1].Value, m.Groups[2].Value );
			}
			else if ( m.Groups[2].Success )
			{
				return String.Format( "<a href=\"{0}\">{0}</a>", m.Groups[2].Value );
			}

			return m.Value;
		}

		public static string UrlToHref( string text )
		{
			if ( text == null )
				return null;

			return m_UrlRegex.Replace( text, new MatchEvaluator( UrlRegex_Match ) );
		}

		public override void OnResponse( Mobile from, string text )
		{
			if ( m_Poller.Active )
			{
				from.SendMessage( "You may not edit an active poll. Deactivate it first." );
			}
			else if ( text == "DEL" )
			{
				if ( m_Option != null )
					m_Poller.RemoveOption( m_Option );
			}
			else
			{
				text = UrlToHref( text );

				if ( m_Option == null )
					m_Poller.AddOption( new ShardPollOption( text ) );
				else
					m_Option.Title = text;
			}

			from.SendGump( new ShardPollGump( from, m_Poller, true, null ) );
		}
	}
}