/*
===========================================================================
Copyright (C) 2015-2019 Project Meteor Dev Team
This file is part of Project Meteor Server.
Project Meteor Server is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Project Meteor Server 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Project Meteor Server. If not, see .
===========================================================================
*/
using Meteor.Common;
using Meteor.Map.actors.area;
using Meteor.Map.actors.group;
using Meteor.Map.Actors;
using Meteor.Map.lua;
using Meteor.Map.packets.send.actor;
using MoonSharp.Interpreter;
using System;
using System.Collections.Generic;
namespace Meteor.Map.actors.director
{
    class Director : Actor
    {
        private uint directorId;
        private string directorScriptPath;
        private List members = new List();
        protected ContentGroup contentGroup;
        private bool isCreated = false;
        private bool isDeleted = false;
        private bool isDeleting = false;
        private Script directorScript;
        private Coroutine currentCoroutine;
        public Director(uint id, Area zone, string directorPath, bool hasContentGroup, params object[] args)
            : base((6 << 28 | zone.ZoneId << 19 | (uint)id + 2))
        {
            directorId = id;
            CurrentArea = zone;
            directorScriptPath = directorPath;
            LoadLuaScript();
            if (hasContentGroup)
                contentGroup = Server.GetWorldManager().CreateContentGroup(this, GetMembers());
            eventConditions = new EventList();
            eventConditions.noticeEventConditions = new List();
            eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeEvent",  0xE,0x0));
            eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeRequest", 0x0, 0x1));
            eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("reqForChild", 0x0, 0x1));            
        }       
        public override SubPacket CreateScriptBindPacket()
        {
            List actualLParams = new List();
            actualLParams.Insert(0, new LuaParam(2, classPath));
            actualLParams.Insert(1, new LuaParam(4, 4));
            actualLParams.Insert(2, new LuaParam(4, 4));
            actualLParams.Insert(3, new LuaParam(4, 4));
            actualLParams.Insert(4, new LuaParam(4, 4));
            actualLParams.Insert(5, new LuaParam(4, 4));
            List lparams = LuaEngine.GetInstance().CallLuaFunctionForReturn(null, this, "init", false);
            for (int i = 1; i < lparams.Count; i++)
                actualLParams.Add(lparams[i]);
            return ActorInstantiatePacket.BuildPacket(Id, Name, className, actualLParams);
        }
        public override List GetSpawnPackets(ushort spawnType = 1)
        {
            List subpackets = new List();
            subpackets.Add(CreateAddActorPacket(0));
            subpackets.AddRange(GetEventConditionPackets());
            subpackets.Add(CreateSpeedPacket());
            subpackets.Add(CreateSpawnPositonPacket(0));
            subpackets.Add(CreateNamePacket());
            subpackets.Add(CreateStatePacket());
            subpackets.Add(CreateIsZoneingPacket());
            subpackets.Add(CreateScriptBindPacket());
            return subpackets;
        }
        public override List GetInitPackets()
        {
            List subpackets = new List();
            SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init");
            initProperties.AddTarget();
            subpackets.Add(initProperties.BuildPacket(Id));
            return subpackets;
        }
        public void OnTalkEvent(Player player, Npc npc)
        {
            LuaEngine.GetInstance().CallLuaFunction(player, this, "onTalkEvent", false, npc);
        }
        public void OnCommandEvent(Player player, Command command)
        {
            LuaEngine.GetInstance().CallLuaFunction(player, this, "onCommandEvent", false, command);
        }   
        public void StartDirector(bool spawnImmediate, params object[] args)
        {
            object[] args2 = new object[args.Length + 1];
            args2[0] = this;
            Array.Copy(args, 0, args2, 1, args.Length);
            List lparams = CallLuaScript("init", args2);
            
            if (lparams != null && lparams.Count >= 1 && lparams[0].value is string)
            {
                classPath = (string)lparams[0].value;
                className = classPath.Substring(classPath.LastIndexOf("/") + 1);
                GenerateActorName((int)directorId);
                isCreated = true;
            }
            if (isCreated && spawnImmediate)
            {
                if (contentGroup != null)
                    contentGroup.Start();
                foreach (Player p in GetPlayerMembers())
                {
                    p.QueuePackets(GetSpawnPackets());
                    p.QueuePackets(GetInitPackets());
                }
            }
            if (this is GuildleveDirector)
            {               
                ((GuildleveDirector)this).LoadGuildleve();
            }
            CallLuaScript("main", this, contentGroup);
        }
        public void StartContentGroup()
        {
            if (contentGroup != null)
                contentGroup.Start();
        }
        public void EndDirector()
        {
            isDeleting = true;
            if (contentGroup != null)
                contentGroup.DeleteGroup();
            if (this is GuildleveDirector)
                ((GuildleveDirector)this).EndGuildleveDirector();
            List players = GetPlayerMembers();
            foreach (Actor player in players)
                ((Player)player).RemoveDirector(this);
            members.Clear();
            isDeleted = true;
            Server.GetWorldManager().GetArea(CurrentArea.ZoneId).DeleteDirector(Id);
        }
        
        public void AddMember(Actor actor)
        {
            if (!members.Contains(actor))
            {
                members.Add(actor);
                if (actor is Player)
                    ((Player)actor).AddDirector(this);
                if (contentGroup != null)
                    contentGroup.AddMember(actor);
            }
        }
        public void RemoveMember(Actor actor)
        {
            if (members.Contains(actor))
                members.Remove(actor);
            if (contentGroup != null)
                contentGroup.RemoveMember(actor.Id);
            if (GetPlayerMembers().Count == 0 && !isDeleting)
                EndDirector();
        }
        public List GetMembers()
        {
            return members;
        }
        public List GetPlayerMembers()
        {
            return members.FindAll(s => s is Player);
        }
        public List GetNpcMembers()
        {
            return members.FindAll(s => s is Npc);
        }
        public bool IsCreated()
        {
            return isCreated;
        }
        public bool IsDeleted()
        {
            return isDeleted;
        }
        public bool HasContentGroup()
        {
            return contentGroup != null;
        }
        public ContentGroup GetContentGroup()
        {
            return contentGroup;
        }
        public void GenerateActorName(int actorNumber)
        {            
            //Format Class Name
            string className = this.className;
                
            className = Char.ToLowerInvariant(className[0]) + className.Substring(1);
            //Format Zone Name
            string zoneName = CurrentArea.ZoneName.Replace("Field", "Fld")
                                           .Replace("Dungeon", "Dgn")
                                           .Replace("Town", "Twn")
                                           .Replace("Battle", "Btl")
                                           .Replace("Test", "Tes")
                                           .Replace("Event", "Evt")
                                           .Replace("Ship", "Shp")
                                           .Replace("Office", "Ofc");
            if (CurrentArea is PrivateArea)
            {
                //Check if "normal"
                zoneName = zoneName.Remove(zoneName.Length - 1, 1) + "P";
            }
            zoneName = Char.ToLowerInvariant(zoneName[0]) + zoneName.Substring(1);
            try
            {
                className = className.Substring(0, 20 - zoneName.Length);
            }
            catch (ArgumentOutOfRangeException)
            { }
            //Convert actor number to base 63
            string classNumber = Utils.ToStringBase63(actorNumber);
            //Get stuff after @
            uint zoneId = CurrentArea.ZoneId;
            int privLevel = CurrentArea.GetPrivateAreaType();
            Name = String.Format("{0}_{1}_{2}@{3:X3}{4:X2}", className, zoneName, classNumber, zoneId, privLevel);
        }
        public string GetScriptPath()
        {
            return directorScriptPath;
        }
        private void LoadLuaScript()
        {
            string errorMsg = "";
            string luaPath = ConfigConstants.OPTIONS_SCRIPTPATH + String.Format(LuaEngine.FILEPATH_DIRECTORS, GetScriptPath());
            directorScript = LuaEngine.LoadScript(luaPath, ref errorMsg);
            if (directorScript == null)
                Program.Log.Error("Could not find script for director {0}.", GetName());          
        }
        private List CallLuaScript(string funcName, params object[] args)
        {
            if (directorScript != null)
            {
                string errorMsg = "";
                directorScript = LuaEngine.LoadScript(ConfigConstants.OPTIONS_SCRIPTPATH + String.Format(LuaEngine.FILEPATH_DIRECTORS, directorScriptPath), ref errorMsg);
                if (!directorScript.Globals.Get(funcName).IsNil())
                {
                    DynValue result = directorScript.Call(directorScript.Globals[funcName], args);
                    List lparams = LuaUtils.CreateLuaParamList(result);
                    return lparams;
                }
                else
                    Program.Log.Error("Could not find script for director {0}.", GetName());
            }
            return null;
        }
        private List StartCoroutine(string funcName, params object[] args)
        {
            if (directorScript != null)
            {
                if (!directorScript.Globals.Get(funcName).IsNil())
                {
                    currentCoroutine = directorScript.CreateCoroutine(directorScript.Globals[funcName]).Coroutine;
                    DynValue value = currentCoroutine.Resume(args);
                    LuaEngine.GetInstance().ResolveResume(null, currentCoroutine, value);
                }
                else
                    Program.Log.Error("Could not find script for director {0}.", GetName());
            }
            return null;
        }
        public void OnEventStart(Player player, object[] args)
        {
            object[] args2 = new object[args.Length + (player == null ? 1 : 2)];
            Array.Copy(args, 0, args2, (player == null ? 1 : 2), args.Length);
            if (player != null)
            {
                args2[0] = player;
                args2[1] = this;
            }
            else
                args2[0] = this;
            Coroutine coroutine = directorScript.CreateCoroutine(directorScript.Globals["onEventStarted"]).Coroutine;
            DynValue value = coroutine.Resume(args2);
            LuaEngine.GetInstance().ResolveResume(player, coroutine, value);
        }
    }    
}