/*
 * 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 com.l2dot.gameserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;


import com.l2dot.Config;
import com.l2dot.L2DatabaseFactory;
import com.l2dot.Server;
import com.l2dot.gameserver.cache.CrestCache;
import com.l2dot.gameserver.cache.HtmCache;
import com.l2dot.gameserver.communitybbs.Manager.ForumsBBSManager;
import com.l2dot.gameserver.datatables.ArmorSetsTable;
import com.l2dot.gameserver.datatables.AugmentationData;
import com.l2dot.gameserver.datatables.BufferSkillsTable;
import com.l2dot.gameserver.datatables.CharNameTable;
import com.l2dot.gameserver.datatables.CharSchemesTable;
import com.l2dot.gameserver.datatables.CharTemplateTable;
import com.l2dot.gameserver.datatables.ClanTable;
import com.l2dot.gameserver.datatables.DoorTable;
import com.l2dot.gameserver.datatables.EventDroplist;
import com.l2dot.gameserver.datatables.ExtractableItemsData;
import com.l2dot.gameserver.datatables.FishTable;
import com.l2dot.gameserver.datatables.HelperBuffTable;
import com.l2dot.gameserver.datatables.HennaTable;
import com.l2dot.gameserver.datatables.HennaTreeTable;
import com.l2dot.gameserver.datatables.HeroSkillTable;
import com.l2dot.gameserver.datatables.ItemTable;
import com.l2dot.gameserver.datatables.LevelUpData;
import com.l2dot.gameserver.datatables.MapRegionTable;
import com.l2dot.gameserver.datatables.MaxCheatersTable;
import com.l2dot.gameserver.datatables.NobleSkillTable;
import com.l2dot.gameserver.datatables.NpcTable;
import com.l2dot.gameserver.datatables.NpcWalkerRoutesTable;
import com.l2dot.gameserver.datatables.SkillSpellbookTable;
import com.l2dot.gameserver.datatables.SkillTable;
import com.l2dot.gameserver.datatables.SkillTreeTable;
import com.l2dot.gameserver.datatables.SpawnTable;
import com.l2dot.gameserver.datatables.StaticObjects;
import com.l2dot.gameserver.datatables.SummonItemsData;
import com.l2dot.gameserver.datatables.TeleportLocationTable;
import com.l2dot.gameserver.datatables.ZoneData;
import com.l2dot.gameserver.geoeditorcon.GeoEditorListener;
import com.l2dot.gameserver.handler.AdminCommandHandler;
import com.l2dot.gameserver.handler.AutoAnnouncementHandler;
import com.l2dot.gameserver.handler.ItemHandler;
import com.l2dot.gameserver.handler.SkillHandler;
import com.l2dot.gameserver.handler.UserCommandHandler;
import com.l2dot.gameserver.handler.VoicedCommandHandler;
import com.l2dot.gameserver.idfactory.IdFactory;
import com.l2dot.gameserver.instancemanager.AuctionManager;
import com.l2dot.gameserver.instancemanager.AwayManager;
import com.l2dot.gameserver.instancemanager.BoatManager;
import com.l2dot.gameserver.instancemanager.CastleManager;
import com.l2dot.gameserver.instancemanager.CastleManorManager;
import com.l2dot.gameserver.instancemanager.ClanHallManager;
import com.l2dot.gameserver.instancemanager.CoupleManager;
import com.l2dot.gameserver.instancemanager.CursedWeaponsManager;
import com.l2dot.gameserver.instancemanager.DayNightSpawnManager;
import com.l2dot.gameserver.instancemanager.DimensionalRiftManager;
import com.l2dot.gameserver.instancemanager.ItemsOnGroundManager;
import com.l2dot.gameserver.instancemanager.MercTicketManager;
import com.l2dot.gameserver.instancemanager.PetitionManager;
import com.l2dot.gameserver.instancemanager.QuestManager;
import com.l2dot.gameserver.instancemanager.RaidBossSpawnManager;
import com.l2dot.gameserver.instancemanager.SiegeManager;
import com.l2dot.gameserver.model.AutoChatHandler;
import com.l2dot.gameserver.model.AutoSpawnHandler;
import com.l2dot.gameserver.model.L2Manor;
import com.l2dot.gameserver.model.L2PetDataTable;
import com.l2dot.gameserver.model.L2World;
import com.l2dot.gameserver.model.entity.Hero;
import com.l2dot.gameserver.model.entity.TvTManager;
import com.l2dot.gameserver.network.L2GameClient;
import com.l2dot.gameserver.network.L2GamePacketHandler;
import com.l2dot.gameserver.pathfinding.geonodes.GeoPathFinding;
import com.l2dot.gameserver.script.faenor.FaenorScriptEngine;
import com.l2dot.gameserver.taskmanager.TaskManager;
import com.l2dot.gameserver.util.DynamicExtension;
import com.l2dot.gameserver.util.FloodProtector;
import com.l2dot.status.Status;
import com.l2jserver.mmocore.network.SelectorConfig;
import com.l2jserver.mmocore.network.SelectorThread;

/**
 * This class ...
 *
 * @version $Revision: 1.29.2.15.2.19 $ $Date: 2005/04/05 19:41:23 $
 */
public class GameServer
{
	private static final Logger _log = Logger.getLogger(GameServer.class.getName());
	private final SelectorThread<L2GameClient> _selectorThread;
	private final SkillTable _skillTable;
	private final ItemTable _itemTable;
	private final NpcTable _npcTable;
	private final HennaTable _hennaTable;
	private final IdFactory _idFactory;
	public static GameServer gameServer;
	private static ClanHallManager _cHManager;
	private final Shutdown _shutdownHandler;
    private final DoorTable _doorTable;
    private final SevenSigns _sevenSignsEngine;
    private final AutoChatHandler _autoChatHandler;
	private final AutoSpawnHandler _autoSpawnHandler;
	private LoginServerThread _loginThread;
    private final HelperBuffTable _helperBuffTable;

	private static Status _statusServer;
	@SuppressWarnings("unused")
	private final ThreadPoolManager _threadpools;

    public static final Calendar dateTimeServerStarted = Calendar.getInstance();

    public long getUsedMemoryMB()
	{
		return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1048576; // 1024 * 1024 = 1048576;
	}

    public SelectorThread<L2GameClient> getSelectorThread()
    {
    	return _selectorThread;
    }

	public ClanHallManager getCHManager()
	{
		return _cHManager;
	}
	
	public GameServer() throws Exception
	{
        gameServer = this;
		_log.finest("used mem:" + getUsedMemoryMB()+"MB" );

		_idFactory = IdFactory.getInstance();
        if (!_idFactory.isInitialized())
        {
            _log.severe("Could not read object IDs from DB. Please Check Your Data.");
            throw new Exception("Could not initialize the ID factory");
        }

        _threadpools = ThreadPoolManager.getInstance();

		new File(Config.DATAPACK_ROOT, "data/clans").mkdirs();
		new File(Config.DATAPACK_ROOT, "data/crests").mkdirs();
        new File("pathnode").mkdirs();

		// start game time control early
		GameTimeController.getInstance();

		// keep the references of Singletons to prevent garbage collection
		CharNameTable.getInstance();

		_itemTable = ItemTable.getInstance();
		if (!_itemTable.isInitialized())
		{
		    _log.severe("Could not find the extraced files. Please Check Your Data.");
		    throw new Exception("Could not initialize the item table");
		}

		ExtractableItemsData.getInstance();
		SummonItemsData.getInstance();


		TradeController.getInstance();
		_skillTable = SkillTable.getInstance();
		if (!_skillTable.isInitialized())
		{
		    _log.severe("Could not find the extraced files. Please Check Your Data.");
		    throw new Exception("Could not initialize the skill table");
		}

		
		//L2EMU_ADD by Rayan. L2J - BigBro
		//if(Config.ALLOW_NPC_WALKERS)
		NpcWalkerRoutesTable.getInstance().load();
		//L2EMU_ADD by Rayan. L2J - BigBro

		RecipeController.getInstance();
		
		L2jMackInfo.showInfo();
		SkillTreeTable.getInstance();
		ArmorSetsTable.getInstance();
		FishTable.getInstance();
		SkillSpellbookTable.getInstance();
		CharTemplateTable.getInstance();
		NobleSkillTable.getInstance();
		HeroSkillTable.getInstance();
		
        //Call to load caches
        HtmCache.getInstance();
        CrestCache.getInstance();
        ClanTable.getInstance();
		_npcTable = NpcTable.getInstance();

		if (!_npcTable.isInitialized())
		{
		    _log.severe("Could not find the extraced files. Please Check Your Data.");
		    throw new Exception("Could not initialize the npc table");
		}

		_hennaTable = HennaTable.getInstance();

		if (!_hennaTable.isInitialized())
		   throw new Exception("Could not initialize the Henna Table");

		HennaTreeTable.getInstance();

		if (!_hennaTable.isInitialized())
		   throw new Exception("Could not initialize the Henna Tree Table");

        _helperBuffTable = HelperBuffTable.getInstance();

        if (!_helperBuffTable.isInitialized())
           throw new Exception("Could not initialize the Helper Buff Table");

        GeoData.getInstance();
        if (Config.GEODATA == 2)
        	GeoPathFinding.getInstance();

        // Load clan hall data before zone data
        _cHManager = ClanHallManager.getInstance();
		CastleManager.getInstance();
		SiegeManager.getInstance();
		
		TeleportLocationTable.getInstance();
		LevelUpData.getInstance();
		L2World.getInstance();
		MaxCheatersTable.getInstance();
		ZoneData.getInstance();
        SpawnTable.getInstance();
        RaidBossSpawnManager.getInstance();
        DayNightSpawnManager.getInstance().notifyChangeMode();
        DimensionalRiftManager.getInstance();
		Announcements.getInstance();
		MapRegionTable.getInstance();
		EventDroplist.getInstance();

		/** Load Manor data */
		L2Manor.getInstance();

		/** Load Manager */
		AuctionManager.getInstance();
		BoatManager.getInstance();
		CastleManorManager.getInstance();
		MercTicketManager.getInstance();
		//PartyCommandManager.getInstance();
		PetitionManager.getInstance();
		QuestManager.getInstance();
		AugmentationData.getInstance();
		if (Config.SAVE_DROPPED_ITEM)
			ItemsOnGroundManager.getInstance();

		if (Config.AUTODESTROY_ITEM_AFTER > 0 || Config.HERB_AUTO_DESTROY_TIME > 0)
    	    ItemsAutoDestroy.getInstance();

        MonsterRace.getInstance();

		_doorTable = DoorTable.getInstance();
		_doorTable.parseData();
        StaticObjects.getInstance();

		_sevenSignsEngine = SevenSigns.getInstance();
        SevenSignsFestival.getInstance();
		_autoSpawnHandler = AutoSpawnHandler.getInstance();
		_autoChatHandler = AutoChatHandler.getInstance();

        // Spawn the Orators/Preachers if in the Seal Validation period.
        _sevenSignsEngine.spawnSevenSignsNPC();

        Olympiad.getInstance();
        Hero.getInstance();
        FaenorScriptEngine.getInstance();
        // Init of a cursed weapon manager
        CursedWeaponsManager.getInstance();

		_log.config("AutoChatHandler: Loaded " + _autoChatHandler.size() + " handlers in total.");
		_log.config("AutoSpawnHandler: Loaded " + _autoSpawnHandler.size() + " handlers in total.");

		// Handlers
		AdminCommandHandler.getInstance();
		ItemHandler.getInstance();
		SkillHandler.getInstance();
		UserCommandHandler.getInstance();
		VoicedCommandHandler.getInstance();
		AutoAnnouncementHandler.getInstance();
		
		if(Config.L2JMOD_ALLOW_WEDDING)
			CoupleManager.getInstance();

        TaskManager.getInstance();

		GmListTable.getInstance();

        // read pet stats from db
        L2PetDataTable.getInstance().loadPetsData();

        Universe.getInstance();

		if (Config.ACCEPT_GEOEDITOR_CONN)
			GeoEditorListener.getInstance();

		_shutdownHandler = Shutdown.getInstance();
		Runtime.getRuntime().addShutdownHook(_shutdownHandler);

		try
        {
            _doorTable.getDoor(24190001).openMe();
            _doorTable.getDoor(24190002).openMe();
            _doorTable.getDoor(24190003).openMe();
            _doorTable.getDoor(24190004).openMe();
            _doorTable.getDoor(23180001).openMe();
            _doorTable.getDoor(23180002).openMe();
            _doorTable.getDoor(23180003).openMe();
            _doorTable.getDoor(23180004).openMe();
            _doorTable.getDoor(23180005).openMe();
            _doorTable.getDoor(23180006).openMe();

            _doorTable.checkAutoOpen();
        }
        catch (NullPointerException e)
        {
            _log.warning("There is errors in your Door.csv file. Update door.csv");
            if (Config.DEBUG)
            	e.printStackTrace();
        }
        ForumsBBSManager.getInstance();
        _log.config("IdFactory: Free ObjectID's remaining: " + IdFactory.getInstance().size());

        // initialize the dynamic extension loader
        try
        {
            DynamicExtension.getInstance();
        } catch (Exception ex)
        {
            _log.log(Level.WARNING, "DynamicExtension could not be loaded and initialized", ex);
        }

        FloodProtector.getInstance();
        TvTManager.getInstance();
        
        /** NPC Buffer by House */
        if (Config.NPCBUFFER_FEATURE_ENABLED)
        {
        	BufferSkillsTable.getInstance();
        	CharSchemesTable.getInstance();
        }
 
        if (Config.ALLOW_AWAY_STATUS) 
        { 
            _log.info("Away System"); 
            AwayManager.getInstance(); 
        }
 		                
		System.gc();
		// maxMemory is the upper limit the jvm can use, totalMemory the size of the current allocation pool, freeMemory the unused memory in the allocation pool
		long freeMem = (Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory()) / 1048576; // 1024 * 1024 = 1048576;
		long totalMem = Runtime.getRuntime().maxMemory() / 1048576;
		_log.info("GameServer Started, free memory "+freeMem+" Mb of "+totalMem+" Mb");

		_loginThread = LoginServerThread.getInstance();
		_loginThread.start();

		final SelectorConfig sc = new SelectorConfig(); 
	    sc.MAX_READ_PER_PASS = Config.MMO_MAX_READ_PER_PASS; 
        sc.MAX_SEND_PER_PASS = Config.MMO_MAX_SEND_PER_PASS; 
	    sc.SLEEP_TIME = Config.MMO_SELECTOR_SLEEP_TIME; 
        sc.HELPER_BUFFER_COUNT = Config.MMO_HELPER_BUFFER_COUNT; 
        final L2GamePacketHandler gph = new L2GamePacketHandler(); 
        _selectorThread = new SelectorThread<L2GameClient>(sc, gph, gph, gph, null); 
        InetAddress bindAddress = null; 
        if (!Config.GAMESERVER_HOSTNAME.equals("*")) 
        { 
           try 
           { 
                 bindAddress = InetAddress.getByName(Config.GAMESERVER_HOSTNAME); 
           } 
           catch (UnknownHostException e1) 
	       { 
	             _log.severe("WARNING: The GameServer bind address is invalid, using all avaliable IPs. Reason: " + e1.getMessage()); 
	       if (Config.DEVELOPER) 
	               e1.printStackTrace(); 
                } 
           } 
	       try 
	       { 
	           _selectorThread.openServerSocket(bindAddress, Config.PORT_GAME); 
	       } 
	       catch (IOException e) 
	       { 
                _log.severe("FATAL: Failed to open server socket. Reason: " + e.getMessage()); 
	       if (Config.DEVELOPER) 
	               e.printStackTrace(); 
	                   System.exit(1); 
	       } 
		_selectorThread.start();
		_log.config("Maximum Numbers of Connected Players: " + Config.MAXIMUM_ONLINE_USERS);
	}

	public static void main(String[] args) throws Exception
    {
		Server.serverMode = Server.MODE_GAMESERVER;
//      Local Constants
		final String LOG_FOLDER = "log"; // Name of folder for log file
		final String LOG_NAME   = "./log.cfg"; // Name of log file

		/*** Main ***/
		// Create log folder
		File logFolder = new File(Config.DATAPACK_ROOT, LOG_FOLDER);
		logFolder.mkdir();

		// Create input stream for log file -- or store file data into memory
		InputStream is =  new FileInputStream(new File(LOG_NAME));
		LogManager.getLogManager().readConfiguration(is);
		is.close();

		// Initialize config
		Config.load();
		L2DatabaseFactory.getInstance();
		gameServer = new GameServer();

		if ( Config.IS_TELNET_ENABLED )
		{
		    _statusServer = new Status(Server.serverMode);
		    _statusServer.start();
		}
		else
		    System.out.println("Telnet server is currently disabled.");
    }
}
