/*
 * This file is part of aion-emu <aion-emu.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu.  If not, see <http://www.gnu.org/licenses/>.
 */
package admincommands;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.log4j.Logger;

import com.aionemu.gameserver.GameServerError;
import com.aionemu.gameserver.configs.administration.AdminConfig;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.network.aion.serverpackets.SM_CUSTOM_PACKET;
import com.aionemu.gameserver.network.aion.serverpackets.SM_CUSTOM_PACKET.PacketElementType;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.chathandlers.AdminCommand;

/**
 * This admin command is used for sending custom packets from server to client.
 * <p/>
 * Sends packets based on xml mappings in folder "./data/packets".<br />
 * Command details: "//send [1]<br />
 *  * 1 - packet mappings name.<br />
 *  *   - 'demo' for file './data/packets/demo.xml'<br />
 *  *   - 'test' for file './data/packets/test.xml'<br />
 *  * Reciever is a targetted by admin player. If target is 'null' or not a Player - sends to admin.<br />
 * <p/> 
 * Created on: 14.07.2009 13:54:46
 *
 * @author Aquanox
 */

public class AdvSendFakeServerPacket extends AdminCommand
{
	private static final Logger logger = Logger.getLogger(AdvSendFakeServerPacket.class);

	private static final File FOLDER = new File("./data/packets");

	private Unmarshaller unmarshaller;

	/**
	 * Create an instance of admin command.
	 *
	 * @throws GameServerError on initialization error
	 */

	public AdvSendFakeServerPacket()
	{
		super("send");
		// init unmrshaller once.
		try
		{
			unmarshaller = JAXBContext.newInstance(Packets.class, Packet.class, Part.class).createUnmarshaller();
		}
		catch (Exception e)
		{
			throw new GameServerError("Failed to initialize unmarshaller.", e);
		}
	}

	/** {@inheritDoc} */

	@Override
	public void executeCommand(final Player admin, String[] params)
	{
		if(admin.getAccessLevel() < AdminConfig.COMMAND_ADVSENDFAKESERVERPACKET)
		{
			PacketSendUtility.sendMessage(admin, "You dont have enough rights to execute this command");
			return;
		}
		
		if (params.length != 1)
		{
			PacketSendUtility.sendMessage(admin, "Example: //send [file] ");
			return;
		}

		final String mappingName = params[0];
		final Player target = getTargetPlayer(admin);

		//logger.debug("Mapping: " + mappingName);
		//logger.debug("Target: " + target);

		File packetsData = new File(FOLDER, mappingName + ".xml");

		if (!packetsData.exists())
		{
			PacketSendUtility.sendMessage(admin, "Mapping with name " + mappingName + " not found");
			return;
		}

		final Packets packetsTemplate;

		try
		{
			packetsTemplate = (Packets) unmarshaller.unmarshal(packetsData);
		}
		catch (JAXBException e)
		{
			logger.error("Unmarshalling error", e);
			return;
		}

		if (packetsTemplate.getPackets().isEmpty())
		{
			PacketSendUtility.sendMessage(admin, "No packets to send.");
			return;
		}

		send(admin, target, packetsTemplate);
	}

	private void send(Player sender,final Player target, Packets packets)
	{
		final String senderObjectId = String.valueOf(sender.getObjectId());
		final String targetObjectId = String.valueOf(target.getObjectId());

		int packetIndex = 0;// first packet should be sent immediately.
		for (final Packet packetTemplate : packets)
		{
			//logger.debug("Processing: " + packetTemplate);

			final SM_CUSTOM_PACKET packet = new SM_CUSTOM_PACKET(packetTemplate.getOpcode());

			for (Part part : packetTemplate.getParts())
			{
				PacketElementType byCode = PacketElementType.getByCode(part.getType());

				String value = part.getValue();

				if (value.indexOf("${objectId}") != -1)
					value = value.replace("${objectId}", targetObjectId);
				if (value.indexOf("${senderObjectId}") != -1)
					value = value.replace("${senderObjectId}", senderObjectId);
				if (value.indexOf("${targetObjectId}") != -1)
					value = value.replace("${targetObjectId}", targetObjectId);

				if (part.getRepeatCount() == 1) // skip loop
				{
					packet.addElement(byCode, value);
				}
				else
				{
					for (int i = 0; i < part.getRepeatCount(); i++)
						packet.addElement(byCode, value);
				}
			}

			ThreadPoolManager.getInstance().schedule(new Runnable()
			{
				@Override
				public void run()
				{
					//logger.debug("Sending: " + packetTemplate);
					PacketSendUtility.sendPacket(target, packet);
				}
			}, packetIndex * packets.getDelay()); //Kamui: this is correct or a mistake?

			packetIndex++;
		}
	}

	private Player getTargetPlayer(Player admin)
	{
		if (admin.getTarget() instanceof Player)
			return (Player) admin.getTarget();
		else
			return admin;
	}

	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name = "packets")
	private static class Packets implements Iterable<Packet>
	{
		@XmlElement(name = "packet")
		private List<Packet> packets = new ArrayList<Packet>();

		@XmlAttribute(name = "delay")
		private long delay = -1;

		public long getDelay()
		{
			return delay;
		}

		public List<Packet> getPackets()
		{
			return packets;
		}

		@SuppressWarnings("unused")
		public boolean add(Packet packet)
		{
			return packets.add(packet);
		}

		@Override
		public Iterator<Packet> iterator()
		{
			return packets.iterator();
		}

		@Override
		public String toString()
		{
			final StringBuilder sb = new StringBuilder();
			sb.append("Packets");
			sb.append("{delay=").append(delay);
			sb.append(", packets=").append(packets);
			sb.append('}');
			return sb.toString();
		}
	}

	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name = "packet")
	private static class Packet
	{
		@XmlElement(name = "part")
		private Collection<Part> parts = new ArrayList<Part>();

		@XmlAttribute(name = "opcode")
		private String opcode = "-1";

		public int getOpcode()
		{
			return Integer.decode(opcode);
		}

		public Collection<Part> getParts()
		{
			return parts;
		}

		@Override
		public String toString()
		{
			final StringBuilder sb = new StringBuilder();
			sb.append("Packet");
			sb.append("{opcode=").append(opcode);
			sb.append(", parts=").append(parts);
			sb.append('}');
			return sb.toString();
		}
	}

	@XmlAccessorType(XmlAccessType.FIELD)
	@XmlRootElement(name = "part")
	private static class Part
	{
		@XmlAttribute(name = "type", required = true)
		private String type = null;

		@XmlAttribute(name = "value", required = true)
		private String value = null;

		@XmlAttribute(name = "repeat", required = true)
		private int repeatCount = 1;

		public char getType()
		{
			return type.charAt(0);
		}

		public String getValue()
		{
			return value;
		}

		public int getRepeatCount()
		{
			return repeatCount;
		}

		@Override
		public String toString()
		{
			final StringBuilder sb = new StringBuilder();
			sb.append("Part");
			sb.append("{type='").append(type).append('\'');
			sb.append(", value='").append(value).append('\'');
			sb.append(", repeatCount=").append(repeatCount);
			sb.append('}');
			return sb.toString();
		}
	}
}
