/* This program 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 2, or (at your option)
 * any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package net.sf.l2j.gameserver.datatables;

import java.io.File;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilderFactory;

import javolution.util.FastList;
import net.sf.l2j.Config;
import net.sf.l2j.gameserver.model.L2Augmentation;
import net.sf.l2j.gameserver.model.L2ItemInstance;
import net.sf.l2j.gameserver.model.L2Skill;
import net.sf.l2j.gameserver.skills.Stats;
import net.sf.l2j.util.Rnd;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

public class AugmentationData
{
	private static final Logger _log = Logger.getLogger(AugmentationData.class.getName());

	private static AugmentationData _instance;

	public static final AugmentationData getInstance()
	{
		if (_instance == null)
		{
			_instance = new AugmentationData();
		}
		return _instance;
	}

	private static final int STAT_START = 1;
	private static final int STAT_END = 14560;
	private static final int STAT_BLOCKSIZE = 3640;
	private static final int STAT_SUBBLOCKSIZE = 91;

    private static final int PURPLE_START = 14578;
    private static final int RED_START = 14685;

	private static final int BASESTAT_STR = 16341;
	private static final int BASESTAT_CON = 16342;
	private static final int BASESTAT_INT = 16343;
	private static final int BASESTAT_MEN = 16344;


	@SuppressWarnings("unchecked")
	private FastList _augmentationStats[];
    private FastList<augmentationSkill> _blueSkills;
    private FastList<augmentationSkill> _purpleSkills;
    private FastList<augmentationSkill> _redSkills;
    private int _skillsCount;

	public AugmentationData()
	{
		_log.info("Initializing AugmentationData.");

		_augmentationStats = new FastList[4];
		_augmentationStats[0] = new FastList<augmentationStat>();
		_augmentationStats[1] = new FastList<augmentationStat>();
		_augmentationStats[2] = new FastList<augmentationStat>();
		_augmentationStats[3] = new FastList<augmentationStat>();

        _blueSkills = new FastList<augmentationSkill>();
        _purpleSkills = new FastList<augmentationSkill>();
        _redSkills = new FastList<augmentationSkill>();

		load();

		_log.info("AugmentationData: Loaded: "+_augmentationStats[0].size()*4+" augmentation stats.");
        _log.info("AugmentationData: Loaded: " + _blueSkills.size() + " blue, " + _purpleSkills.size() + " purple and " + _redSkills.size() + " red skills"); 	}

	public class augmentationSkill
	{
		private int _skillId;
		private int _maxSkillLevel;
		private int _augmentationSkillId;

		public augmentationSkill(int skillId, int maxSkillLevel, int augmentationSkillId)
		{
			_skillId = skillId;
			_maxSkillLevel = maxSkillLevel;
			_augmentationSkillId = augmentationSkillId;
		}

		public L2Skill getSkill(int level)
		{
			if (level > _maxSkillLevel)
				return SkillTable.getInstance().getInfo(_skillId, _maxSkillLevel);
			return SkillTable.getInstance().getInfo(_skillId, level);
		}

		public int getAugmentationSkillId()
		{
			return _augmentationSkillId;
		}
	}

	public class augmentationStat
	{
		private Stats _stat;
		private int _singleSize;
		private int _combinedSize;
		private float _singleValues[];
		private float _combinedValues[];

		public augmentationStat(Stats stat, float sValues[], float cValues[])
		{
			_stat = stat;
			_singleSize = sValues.length;
			_singleValues = sValues;
			_combinedSize = cValues.length;
			_combinedValues = cValues;
		}

		public int getSingleStatSize()
		{
			return _singleSize;
		}
		public int getCombinedStatSize()
		{
			return _combinedSize;
		}

		public float getSingleStatValue(int i)
		{
			if (i >= _singleSize || i < 0) return _singleValues[_singleSize-1];
			return _singleValues[i];
		}
		public float getCombinedStatValue(int i)
		{
			if (i >= _combinedSize || i < 0) return _combinedValues[_combinedSize-1];
			return _combinedValues[i];
		}

		public Stats getStat()
		{
			return _stat;
		}
	}


	@SuppressWarnings("unchecked") private final void load()
	{

		try
		{
			SkillTable st = SkillTable.getInstance();
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(false);
			factory.setIgnoringComments(true);

			File file = new File(Config.DATAPACK_ROOT+"/data/stats/augmentation/augmentation_skillmap.xml");
			if (!file.exists())
			{
				if (Config.DEBUG)
					System.out.println("The augmentation skillmap file is missing.");
				return;
			}

			Document doc = factory.newDocumentBuilder().parse(file);

			for (Node n=doc.getFirstChild(); n != null; n = n.getNextSibling())
			{
				if ("list".equalsIgnoreCase(n.getNodeName()))
				{
					for (Node d=n.getFirstChild(); d != null; d = d.getNextSibling())
					{
						if ("augmentation".equalsIgnoreCase(d.getNodeName()))
						{
							NamedNodeMap attrs = d.getAttributes();
							int skillId=0, augmentationId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());

							for (Node cd=d.getFirstChild(); cd != null; cd = cd.getNextSibling())
							{
								if ("skillId".equalsIgnoreCase(cd.getNodeName()))
								{
									attrs = cd.getAttributes();
                            		skillId = Integer.parseInt(attrs.getNamedItem("val").getNodeValue());
								}

							}

						    if (augmentationId < PURPLE_START)
                               _blueSkills.add(new augmentationSkill(skillId, st.getMaxLevel(skillId, 1), augmentationId));
                            else if (augmentationId < RED_START)
                               _purpleSkills.add(new augmentationSkill(skillId, st.getMaxLevel(skillId, 1), augmentationId));
                            else
                               _redSkills.add(new augmentationSkill(skillId, st.getMaxLevel(skillId, 1), augmentationId)); 						}
					}
				}
			}
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error parsing augmentation_skillmap.xml.", e);
			return ;
		}



		for (int i=1; i<5; i++)
		{
			try
			{
				DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
				factory.setValidating(false);
				factory.setIgnoringComments(true);

				File file = new File(Config.DATAPACK_ROOT+"/data/stats/augmentation/augmentation_stats"+i+".xml");
				if (!file.exists())
				{
					if (Config.DEBUG)
						System.out.println("The augmentation stat data file "+i+" is missing.");
					return;
				}

				Document doc = factory.newDocumentBuilder().parse(file);

				for (Node n=doc.getFirstChild(); n != null; n = n.getNextSibling())
				{
					if ("list".equalsIgnoreCase(n.getNodeName()))
					{
						for (Node d=n.getFirstChild(); d != null; d = d.getNextSibling())
						{
							if ("stat".equalsIgnoreCase(d.getNodeName()))
							{
								NamedNodeMap attrs = d.getAttributes();
								String statName = attrs.getNamedItem("name").getNodeValue();
								float soloValues[]=null, combinedValues[]=null;

								for (Node cd=d.getFirstChild(); cd != null; cd = cd.getNextSibling())
								{
									if ("table".equalsIgnoreCase(cd.getNodeName()))
									{
										attrs = cd.getAttributes();
										String tableName = attrs.getNamedItem("name").getNodeValue();

										StringTokenizer data = new StringTokenizer(cd.getFirstChild().getNodeValue());
										List<Float> array = new FastList<Float>();
										while (data.hasMoreTokens())
											array.add(Float.parseFloat(data.nextToken()));

										if (tableName.equalsIgnoreCase("#soloValues"))
										{
											soloValues = new float[array.size()];
											int x=0;
											for (float value : array)
												soloValues[x++] = value;
										}
										else
										{
											combinedValues = new float[array.size()];
											int x=0;
											for (float value : array)
												combinedValues[x++] = value;
										}
									}
								}
								((FastList<augmentationStat>)_augmentationStats[(i-1)]).add(new augmentationStat(Stats.valueOfXml(statName), soloValues, combinedValues));
							}
						}
					}
				}
			}
			catch (Exception e)
			{
				_log.log(Level.SEVERE, "Error parsing augmentation_stats"+i+".xml.", e);
				return ;
			}
		}
	}


	public L2Augmentation generateRandomAugmentation(L2ItemInstance item, int lifeStoneLevel, int lifeStoneGrade)
	{

        int skill_Chance = 0;
        int stat34 = 0;
        boolean generateSkill = false;
        int resultColor = 0;
        boolean generateGlow = false;
        switch (lifeStoneGrade)
        {
 case 0:
    skill_Chance = Config.AUGMENTATION_NG_SKILL_CHANCE;
    if (Rnd.get(1,100) <= Config.AUGMENTATION_NG_GLOW_CHANCE)
       generateGlow = true;
    break;
 case 1:
    skill_Chance = Config.AUGMENTATION_MID_SKILL_CHANCE;
    if (Rnd.get(1,100) <= Config.AUGMENTATION_MID_GLOW_CHANCE)
       generateGlow = true;
    break;
 case 2:
    skill_Chance = Config.AUGMENTATION_HIGH_SKILL_CHANCE;
    if (Rnd.get(1,100) <= Config.AUGMENTATION_HIGH_GLOW_CHANCE)
       generateGlow = true;
    break;
 case 3:
    skill_Chance = Config.AUGMENTATION_TOP_SKILL_CHANCE;
    if (Rnd.get(1,100) <= Config.AUGMENTATION_TOP_GLOW_CHANCE)
       generateGlow = true;
}

if (Rnd.get(1, 100) <= skill_Chance)
   generateSkill = true;
else if (Rnd.get(1, 100) <= Config.AUGMENTATION_BASESTAT_CHANCE)
   stat34 = Rnd.get(BASESTAT_STR, BASESTAT_MEN);

if (stat34 == 0 && !generateSkill)
{
        resultColor = Rnd.get(0, 100);
        if (resultColor <= 15 * lifeStoneGrade + 40)
                resultColor = 1;
         else
                 resultColor = 0;
}
else
{
         resultColor = Rnd.get(0, 100);
         if (resultColor <= 10 * lifeStoneGrade + 5 || stat34 != 0)
                 resultColor = 3;
         else if (resultColor <= 10 * lifeStoneGrade + 10)
                 resultColor = 1;
         else
                 resultColor = 2;
}

        int stat12 = 0;

		if (stat34 == 0 && !generateSkill)
		{
            int temp = Rnd.get(2,3);
            int colorOffset = resultColor * 10 * STAT_SUBBLOCKSIZE + temp * STAT_BLOCKSIZE + 1;
            int offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + colorOffset;

            stat34 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
             if (generateGlow && lifeStoneGrade >= 2)
                     offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + (temp - 2) * STAT_BLOCKSIZE + lifeStoneGrade * 10 * STAT_SUBBLOCKSIZE + 1;
             else
                     offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + (temp - 2) * STAT_BLOCKSIZE + Rnd.get(0, 1) * 10 * STAT_SUBBLOCKSIZE + 1;
             stat12 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
    }
    else
    {
             int offset;
             if (!generateGlow)
                     offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + Rnd.get(0, 1) * STAT_BLOCKSIZE + 1;
             else
                     offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + Rnd.get(0, 1) * STAT_BLOCKSIZE + (lifeStoneGrade + resultColor) / 2 * 10 * STAT_SUBBLOCKSIZE+ 1;
             stat12 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
    }

		L2Skill skill = null;
		if (generateSkill)
		{
			augmentationSkill temp=null;
            switch (resultColor)
            {
                case 1:
                    temp = _blueSkills.get(Rnd.get(0, _blueSkills.size() - 1)); 					skill = temp.getSkill(lifeStoneLevel);
                    stat34 = temp.getAugmentationSkillId() + (lifeStoneLevel - 1) * _skillsCount;
					break;
                case 2:
                    temp = _purpleSkills.get(Rnd.get(0, _purpleSkills.size() - 1)); 					skill = temp.getSkill(lifeStoneLevel);
                    stat34 = temp.getAugmentationSkillId() + (lifeStoneLevel - 1) * _skillsCount;
                    break;
                case 3:
                    temp = _redSkills.get(Rnd.get(0, _redSkills.size() - 1)); 				skill = temp.getSkill(lifeStoneLevel);
                    stat34 = temp.getAugmentationSkillId() + (lifeStoneLevel - 1) * _skillsCount;
                    break;
			}
		}
        if (Config.DEBUG)
            _log.info("Augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; resultColor=" + resultColor + "; level=" + lifeStoneLevel + "; grade=" + lifeStoneGrade);
		return new L2Augmentation(item, ((stat34<<16)+stat12), skill, true);
	}

	public class AugStat
	{
		private Stats _stat;
		private float _value;
		public AugStat(Stats stat, float value)
		{
			_stat = stat; _value = value;
		}
		public Stats getStat() { return _stat; }
		public float getValue() { return _value; }
	}

	public FastList<AugStat> getAugStatsById(int augmentationId)
	{
		FastList <AugStat> temp = new FastList<AugStat>();
		int stats[] = new int[2];
		stats[0] = 0x0000FFFF&augmentationId;
		stats[1] = augmentationId>>16;

		for (int i=0; i<2; i++)
		{

			if (stats[i] >= STAT_START && stats[i] <= STAT_END)
			{
				int block=0;
				while (stats[i] > STAT_BLOCKSIZE)
				{
					stats[i]-=STAT_BLOCKSIZE;
					block++;
				}

				int subblock=0;
				while (stats[i] > STAT_SUBBLOCKSIZE)
				{
					stats[i]-=STAT_SUBBLOCKSIZE;
					subblock++;
				}

				if (stats[i] < 14)
				{
					augmentationStat as = (augmentationStat)_augmentationStats[block].get((stats[i]-1));
					temp.add(new AugStat(as.getStat(), as.getSingleStatValue(subblock)));
				}
				else
				{
					stats[i]-=13;
					int x = 12;
					int rescales = 0;

					while (stats[i] > x)
					{
						stats[i]-=x;
						x--;
						rescales++;
					}

					augmentationStat as = (augmentationStat)_augmentationStats[block].get(rescales);
					if (rescales == 0)
						temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock)));
					else
						temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock*2+1)));


					as = (augmentationStat)_augmentationStats[block].get(rescales+stats[i]);
					if (as.getStat() == Stats.CRITICAL_DAMAGE)
						temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock)));
					else
						temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock*2)));
				}
			}

			else if (stats[i] >= BASESTAT_STR && stats[i] <= BASESTAT_MEN)
			{
				switch (stats[i])
				{
					case BASESTAT_STR:
						temp.add(new AugStat(Stats.STAT_STR, 1.0f));
						break;
					case BASESTAT_CON:
						temp.add(new AugStat(Stats.STAT_CON, 1.0f));
						break;
					case BASESTAT_INT:
						temp.add(new AugStat(Stats.STAT_INT, 1.0f));
						break;
					case BASESTAT_MEN:
						temp.add(new AugStat(Stats.STAT_MEN, 1.0f));
						break;
				}
			}
		}

		return temp;
	}
}