/**
 * Copyright © 2010, Res Interactive, LLC. All Rights Reserved.
 */
package com.tootsville.npc;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;

import org.starhope.appius.except.GameLogicException;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.except.PrivilegeRequiredException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.game.Zone;
import org.starhope.appius.game.inventory.Inventory;
import org.starhope.appius.game.inventory.InventoryItem;
import org.starhope.appius.geometry.Coord3D;
import org.starhope.appius.room.Room;
import org.starhope.appius.sys.admin.SecurityCapability;
import org.starhope.appius.user.AbstractNonPlayerCharacter;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.events.ActionHandler;
import org.starhope.appius.user.events.ActionMethod;
import org.starhope.appius.user.events.EventRecord;
import org.starhope.appius.user.events.Quaestor;
import org.starhope.util.LibMisc;

/**
 * @author brpocock@star-hope.org
 */
public class Harmony extends AbstractNonPlayerCharacter {

	/**
	 * WRITEME: Document this type.
	 *
	 * @author brpocock@star-hope.org
	 */
	static final class EnterForestRunner implements Runnable {
		/**
		 * WRITEME: Document this brpocock@star-hope.org
		 */
		private final Harmony harmony;

		/**
		 * WRITEME: Document this constructor brpocock@star-hope.org
		 *
		 * @param anHarmony WRITEME
		 */
		EnterForestRunner (final Harmony anHarmony) {
			harmony = anHarmony;
		}

		/**
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run () {
			harmony.enterEnchantedForest ();
		}
	}

	/**
	 * WRITEME: Document this type.
	 *
	 * @author brpocock@star-hope.org
	 */
	static final class GoAwayRunner implements Runnable {
		/**
		 * WRITEME: Document this brpocock@star-hope.org
		 */
		private final Harmony harmony;

		/**
		 * WRITEME: Document this constructor brpocock@star-hope.org
		 *
		 * @param anHarmony the NPC
		 */
		GoAwayRunner (final Harmony anHarmony) {
			harmony = anHarmony;
		}

		/**
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run () {
			harmony.goAway ();
		}
	}

	/**
	 * WRITEME: Document this type.
	 *
	 * @author brpocock@star-hope.org
	 */
	final static class GoNowhere implements Runnable {
		/**
		 * WRITEME: Document this brpocock@star-hope.org
		 */
		private final Harmony harmony;

		/**
		 * WRITEME: Document this constructor brpocock@star-hope.org
		 *
		 * @param anHarmony Harmony
		 */
		GoNowhere (final Harmony anHarmony) {
			harmony = anHarmony;
		}

		/**
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run () {
			try {
				final Room nowhere = harmony.getRoom ().getZone ()
				.getRoomByName ("nowhere");
				nowhere.join (harmony);
				harmony.setRoom (nowhere);
			} catch (final NotFoundException e) {
				AppiusClaudiusCaecus
				.reportBug (
						"Caught a NotFoundException looking for nowhere ",
						e);
			}
			harmony.scheduleNextAppearance ();
		}
	}

	/**
	 * WRITEME: Document this type.
	 *
	 * @author brpocock@star-hope.org
	 */
	static final class KidWalksUpTo implements ActionMethod {
		/**
		 * WRITEME: Document this brpocock@star-hope.org
		 */
		private final Harmony harmony;

		/**
		 * WRITEME: Document this constructor brpocock@star-hope.org
		 *
		 * @param anHarmony WRITEME
		 */
		KidWalksUpTo (final Harmony anHarmony) {
			harmony = anHarmony;
		}

		/**
		 * @see org.starhope.appius.user.events.ActionMethod#acceptAction(org.starhope.appius.room.Room,
		 *      org.starhope.appius.user.AbstractUser, java.lang.String,
		 *      org.starhope.appius.user.AbstractUser, java.lang.String,
		 *      java.lang.Object[])
		 */
		@Override
		public boolean acceptAction (final Room where,
				final AbstractUser subject, final String verb,
				final AbstractUser object, final String indirectObject,
				final Object... trailer) {
			if ( !harmony.getRoom ().equals (where)) return false;
			if (subject.isNPC ()) return false;
			if (harmony.isBuddy (subject)) {
				final Inventory inventory = subject.getInventory ();
				final InventoryItem cleanupItem = inventory
				.findItem (Harmony.CLEANUP_ITEM_ID);
				if (null == cleanupItem) {
					giveKidBucket (subject);
					return false;
				}
				final List <Integer> events = Quaestor
				.getStartedEventsByType (subject,
						Harmony.EVENT_MONIKER);
				if (events.size () == 0) {
					if (Harmony.saidNotCleaned.contains (subject)) return false;
					Harmony.saidNotCleaned.add (subject);
					harmony.setLastSpoken ();
					subject.acceptPublicMessage (harmony, String
							.format (LibMisc
									.getText ("npc.Harmony.noClean"),
									subject.getAvatarLabel ()));
					return false;
				}
				payKidForCleanup (subject, events);
				final int overall = Quaestor.getEndedEventCount (
						subject, Harmony.EVENT_MONIKER);
				if (overall > Harmony.LEVEL_1_COUNT) {
					if (null == inventory
							.findItem (Harmony.LEVEL_1_ITEM)) {
						levelUp_level1 (subject);
						return false;
					}
				}
				if (overall > Harmony.LEVEL_2_COUNT) {
					if (null == inventory
							.findItem (Harmony.LEVEL_2_ITEM)) {
						levelUp_level2 (subject);
						return false;
					}
				}
				harmony.setLastSpoken ();
				BigDecimal sum = BigDecimal.ZERO;
				for (final Integer e : events) {
					try {
						sum = sum.add (Quaestor.getEventByID (
								e.intValue ())
								.getCurrencyAmountEarned ());
					} catch (final NotFoundException e1) {
						AppiusClaudiusCaecus
						.reportBug (
								"Caught a NotFoundException in KidWalksUpTo.acceptAction ",
								e1);
					}
				}
				subject.acceptPublicMessage (harmony, String.format (
						LibMisc.getText ("npc.Harmony.cleaning"),
						Integer.valueOf (events.size ()), subject
						.getAvatarLabel (), Integer
						.valueOf (sum.intValue ())));
			} else {
				harmony.speakCasually ("proximity");
				harmony.inviteBuddy (subject);
			}
			return false;
		}

		/**
		 * WRITEME: Document this method brpocock@star-hope.org
		 *
		 * @param subject WRITEME
		 */
		private void giveKidBucket (final AbstractUser subject) {
			final InventoryItem cleanupItem = harmony.getInventory ()
			.add (Harmony.CLEANUP_ITEM_ID);
			harmony.setLastSpoken ();
			subject.acceptPublicMessage (harmony, LibMisc
					.getText ("npc.Harmony.buddy0"));
			cleanupItem.setOwner (subject);
			final Inventory kidsStuff = subject.getInventory ();
			kidsStuff.add (cleanupItem);
			kidsStuff.don(cleanupItem,null);
			subject.sendWardrobe ();
			harmony.speakCasually ("buddy0");
			harmony.speakCasually ("buddy1");
			harmony.speakCasually ("buddy2");
		}

		/**
		 * WRITEME: Document this method brpocock@star-hope.org
		 *
		 * @param subject the user leveling up
		 */
		private void levelUp_level1 (final AbstractUser subject) {
			harmony.setLastSpoken ();
			subject.acceptPublicMessage (harmony, LibMisc
					.getText ("npc.Harmony.level1"));
			final InventoryItem level1Item = harmony.getInventory ()
			.add (Harmony.LEVEL_1_ITEM);
			try {
				final EventRecord gift = Quaestor.startEvent (subject,
						"gift");
				gift.end (level1Item, harmony);
			} catch (final Exception e) {
				AppiusClaudiusCaecus.reportBug (
						"Caught a Exception in .acceptAction ", e);
			}
		}

		/**
		 * WRITEME: Document this method brpocock@star-hope.org
		 *
		 * @param subject the user leveling up
		 */
		private void levelUp_level2 (final AbstractUser subject) {
			harmony.setLastSpoken ();
			subject.acceptPublicMessage (harmony, LibMisc
					.getText ("npc.Harmony.level2"));
			final InventoryItem level2Item = harmony.getInventory ()
			.add (Harmony.LEVEL_2_ITEM);
			try {
				final EventRecord gift = Quaestor.startEvent (subject,
						"gift");
				gift.end (level2Item, harmony);
			} catch (final Exception e) {
				AppiusClaudiusCaecus.reportBug (
						"Caught a Exception in .acceptAction ", e);
			}
		}

		/**
		 * WRITEME: Document this method brpocock@star-hope.org
		 *
		 * @param kid the player who is being paid off
		 * @param events the collection of events for which s/he is
		 *            being paid
		 */
		private void payKidForCleanup (final AbstractUser kid,
				final List <Integer> events) {

			if ( !Harmony.saidNeedMore.contains (kid)) {
				harmony.speakCasually ("postReward");
				Harmony.saidNeedMore.add (kid);
			}
			REWARDS: for (final Integer eventID : events) {
				EventRecord ev;
				try {
					ev = Quaestor.getEventByID (eventID.intValue ());
				} catch (final NotFoundException e) {
					AppiusClaudiusCaecus
					.reportBug (
							"Caught a NotFoundException in KidWalksUpTo.acceptAction ",
							e);
					continue REWARDS;
				}
				ev.end (BigInteger.ONE);
			}
		}
	}

	/**
	 * WRITEME: Document this type.
	 *
	 * @author brpocock@star-hope.org
	 */
	static final class SitOnTuffetRunner implements Runnable {
		/**
		 * WRITEME: Document this brpocock@star-hope.org
		 */
		private final Harmony harmony;

		/**
		 * WRITEME: Document this constructor brpocock@star-hope.org
		 *
		 * @param anHarmony the npc
		 */
		SitOnTuffetRunner (final Harmony anHarmony) {
			harmony = anHarmony;
		}

		/**
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run () {
			harmony.sitOnTuffet ();
		}
	}

	/**
	 * WRITEME
	 */
	protected static final int CLEANUP_ITEM_ID = 2420;

	/**
	 * WRITEME
	 */
	public static final String EVENT_MONIKER = "cleanedSmudge";

	/**
	 * Counter for getting instance ID's
	 */
	protected static final AtomicInteger instanceCounter = new AtomicInteger ();

	/**
	 * WRITEME
	 */
	public static final int LEVEL_1_COUNT = 25;

	/**
	 * WRITEME
	 */
	public static final int LEVEL_1_ITEM = 2421;

	/**
	 * WRITEME
	 */
	public static final int LEVEL_2_COUNT = 100;

	/**
	 * WRITEME
	 */
	public static final int LEVEL_2_ITEM = 2455;

	/**
	 * Don't nag them about not cleaning up more, once we know about it
	 */
	final static Set <AbstractUser> saidNeedMore = new ConcurrentSkipListSet <AbstractUser> ();

	/**
	 * Don't nag them about not cleaning up more, once we know about it
	 */
	final static Set <AbstractUser> saidNotCleaned = new ConcurrentSkipListSet <AbstractUser> ();

	/**
	 * WRITEME: Document this brpocock@star-hope.org
	 */
	private static final long serialVersionUID = -1963742234389687039L;

	/**
	 * handler for kids walking onto her Tuffet
	 */
	private final ActionHandler kidWalksUpHandler = new ActionHandler (getRoom (), null,
			"event.srv.enter/evt_$harmonyTuffet", null,
			new KidWalksUpTo (this));

	/**
	 * WRITEME: Document this brpocock@star-hope.org
	 */
	private long nextAppearance = -1;

	/**
	 * WRITEME: Document this brpocock@star-hope.org
	 */
	private long nextExit = -1;

	/**
	 * Time between emitting casual speech texts in ms
	 */
	{
		casualSpeechRate = 7320;
	}

	/**
	 * WRITEME brpocock@star-hope.org
	 *
	 * @param z WRITEME
	 * @throws GameLogicException WRITEME
	 * @throws NotFoundException WRITEME
	 */
	public Harmony (final Zone z) throws NotFoundException,
	GameLogicException {
		super ("Harmony");
		currentRoom = z.getRoom ("enchantedForest");
		currentRoom.join (this);
		final Calendar calendar = Calendar.getInstance (TimeZone
				.getTimeZone ("America/New_York"), Locale.ENGLISH);
		calendar.setTimeInMillis (System.currentTimeMillis ());
		final int hour = calendar.get (Calendar.HOUR_OF_DAY);
		if ( (hour & 1) == 1) {
			enterEnchantedForest ();
		} else {
			scheduleNextAppearance ();
			goAway ();
		}
	}

	/**
	 * @see org.starhope.appius.user.AbstractNonPlayerCharacter#acceptPublicMessage(org.starhope.appius.user.AbstractUser,
	 *      java.lang.String)
	 */
	@Override
	public void acceptPublicMessage (final AbstractUser from,
			final String message) {
		final String lcase = message.toLowerCase (Locale.ENGLISH);
		if (lcase.contains ("smudge") || lcase.contains (
		"shade")
		|| lcase.contains (
		"shadow")
		|| lcase.contains (
		"wellduh")) {
			speakCasually ("hearSmudge");
		}
		if ("/peace".equals (message) || lcase.contains ("Harmony")) {
			speakCasually ("peaceReply");
		}
		if ("Good-bye, Harmony".equals (message)
				&& org.starhope.appius.sys.admin.Security
				.hasCapability (
						from,
						SecurityCapability.CAP_CHARACTER_COMMANDS)) {
			speak (getRoom (), "Good-bye, " + from.getAvatarLabel ());
			casualSpeechQueue.clear ();
			goAway ();
		}
		if ("Hello, Harmony".equals (message)
				&& org.starhope.appius.sys.admin.Security
				.hasCapability (
						from,
						SecurityCapability.CAP_CHARACTER_COMMANDS)) {
			casualSpeechQueue.clear ();
			enterEnchantedForest ();
		}
	}

	/**
	 * @see org.starhope.appius.user.AbstractUser#ban(org.starhope.appius.user.AbstractUser,
	 *      java.lang.String)
	 */
	@Override
	public void ban (final AbstractUser u, final String banReason)
	throws PrivilegeRequiredException {
		destroy ();
	}

	/**
	 * enters Enchanted Forest from “stage left” where we imagine her
	 * house must be.
	 */
	public void enterEnchantedForest () {
		Room forest;
		final Zone zone = getZone ();
		if (null == zone) return;
		try {
			forest = getZone ().getRoomByName ("enchantedForest");
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus
			.reportBug (
					"Caught a NotFoundException in Harmony.enterEnchantedForest ",
					e);
			return;
		}
		setLocation (new Coord3D ( -100, -100, -100));
		forest.join (this, "user~harmony~0");
		setRoom (forest);
		forest.putHere (this, new Coord3D (0, 400, 0));
		if (forest.getPlaceStringByName ("evt_$harmonyTuffet") == null) {
			forest.goTo (this, new Coord3D (400, 500, 0), null, "Walk");
			// forest.setPlace ("evt_$harmonyTuffet",
			// "400,400~500,400~500,500~400,500~400,400");
		} else {
			forest.goTo (this, "evt_$harmonyTuffet", "Walk");
		}
		final Harmony harmony = this;
		AppiusClaudiusCaecus
		.getKalendor ()
		.schedule (
				getEndMovementTime (System.currentTimeMillis ()) + 1000,
				new SitOnTuffetRunner (harmony));
		scheduleNextAppearance ();
		scheduleExit ();
	}

	/**
	 * @see org.starhope.appius.user.AbstractNonPlayerCharacter#getInstanceID()
	 */
	@Override
	protected int getInstanceID () {
		return Harmony.instanceCounter.incrementAndGet ();
	}

	/**
	 * Leave…
	 */
	public void goAway () {
		getRoom ().goTo (this,
				new Coord3D ( -100, getLocation ().getY (), 0), null,
				"Walk");
		final Harmony harmony = this;
		AppiusClaudiusCaecus.getKalendor ().schedule (
				getEndMovementTime (System.currentTimeMillis ()),
				new GoNowhere (harmony));
	}

	/**
	 * Schedule to leave on the next even-numbered hour
	 */
	public void scheduleExit () {
		if (nextExit > System.currentTimeMillis ()) return;
		final Calendar calendar = Calendar.getInstance (TimeZone
				.getTimeZone ("America/New_York"), Locale.ENGLISH);
		calendar.setTimeInMillis (System.currentTimeMillis ());
		calendar.add (Calendar.HOUR, 1);
		final int hour = calendar.get (Calendar.HOUR_OF_DAY);
		if ( (hour & 1) == 1) {
			calendar.add (Calendar.HOUR, 1);
		}
		final Harmony harmony = this;
		nextExit = AppiusClaudiusCaecus.getKalendor ()
		.schedule (calendar.getTimeInMillis (),
				new GoAwayRunner (harmony));
	}

	/**
	 * Schedule to appear on the next odd-numbered hour
	 */
	public void scheduleNextAppearance () {
		if (nextAppearance > System.currentTimeMillis ()) return;
		final Calendar calendar = Calendar.getInstance (TimeZone
				.getTimeZone ("America/New_York"), Locale.ENGLISH);
		calendar.setTimeInMillis (System.currentTimeMillis ());
		calendar.add (Calendar.HOUR, 1);
		final int hour = calendar.get (Calendar.HOUR_OF_DAY);
		if ( (hour & 1) == 0) {
			calendar.add (Calendar.HOUR, 1);
		}
		final Harmony harmony = this;
		nextAppearance = AppiusClaudiusCaecus.getKalendor ().schedule (
				calendar.getTimeInMillis (),
				new EnterForestRunner (harmony));
	}

	/**
	 * WRITEME: Document this method brpocock@star-hope.org
	 */
	protected void setLastSpoken () {
		lastSpoken = System.currentTimeMillis ();
	}

	/**
	 * WRITEME: Document this method brpocock@star-hope.org
	 */
	protected void sitOnTuffet () {
		getRoom ().goTo (this, getLocation (), "S", "Sit");
		getRoom ().goTo (this, getLocation (), "S", "Sit");
		speakCasually ("greet");
		Quaestor.ignore (kidWalksUpHandler);
		Quaestor.listen (kidWalksUpHandler);
	}

}
