using System;
using System.Collections.Generic;
using System.Linq;

using Server;
using Server.Mobiles;
using Server.Multis;
using Server.Spells;

namespace Server.Items
{
    public abstract class MannedCannon : Item
    {
        public virtual TimeSpan ScanDelay { get { return TimeSpan.FromSeconds(Utility.RandomMinMax(5, 10)); } }

        [CommandProperty(AccessLevel.GameMaster)]
        public Mobile Operator { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public Direction Facing { get { return GetFacing(); } }

        public DateTime NextScan { get; set; }
        public bool CanFireUnmanned { get; set; }

        public abstract CannonPower Power { get; }
        public abstract int Range { get; }

        public virtual AmmunitionType AmmoType { get { return AmmunitionType.Cannonball; } }
        public virtual int LateralOffset { get { return 1; } }

        public MannedCannon(Mobile opera, Direction facing)
            : base(0)
        {
            ItemID = GetID(facing, Power);
            Operator = opera;

            Movable = false;
        }

        private static int GetID(Direction facing, CannonPower power)
        {
            switch (facing)
            {
                default:
                case Direction.South:
                    return BaseGalleon.CannonIDs[0][(int)power];
                case Direction.West:
                    return BaseGalleon.CannonIDs[1][(int)power];
                case Direction.North:
                    return BaseGalleon.CannonIDs[2][(int)power];
                case Direction.East:
                    return BaseGalleon.CannonIDs[3][(int)power];
            }
        }

        public Direction GetFacing()
        {
            if (BaseGalleon.CannonIDs[0].Any(id => id == ItemID))
            {
                return Direction.South;
            }
            if (BaseGalleon.CannonIDs[1].Any(id => id == ItemID))
            {
                return Direction.West;
            }
            if (BaseGalleon.CannonIDs[2].Any(id => id == ItemID))
            {
                return Direction.North;
            }

            return Direction.East;
        }

        public bool Scan(bool shoot)
        {
            var targets = AcquireTarget();
            bool acquiredTarget = false;

            if (targets != null && targets.Length > 0)
            {
                foreach (var t in targets)
                {
                    if (t.Entity is BaseGalleon && AmmoType != AmmunitionType.Grapeshot)
                    {
                        if (shoot)
                        {
                            DoShootEffects();
                            TimeSpan delay = TimeSpan.FromSeconds((double)t.Range / 10.0);

                            Timer.DelayCall(delay, new TimerStateCallback(OnShipHit), new object[] { (BaseGalleon)t.Entity, t.Location, AmmoType });
                        }

                        acquiredTarget = true;
                    }
                    else if (t.Entity is Mobile && AmmoType == AmmunitionType.Grapeshot)
                    {
                        var m = t.Entity as Mobile;

                        if (shoot)
                        {
                            DoShootEffects();
                            TimeSpan delay = TimeSpan.FromSeconds((double)t.Range / 10.0);

                            Timer.DelayCall(delay, new TimerStateCallback(OnMobileHit), new object[] { m, t.Location, AmmoType });
                        }

                        acquiredTarget = true;
                    }
                }
            }

            return acquiredTarget;
        }

        public Target[] AcquireTarget()
        {
            AmmoInfo ammo = AmmoInfo.GetAmmoInfo(AmmoType);

            int xOffset = 0; int yOffset = 0;
            int currentRange = 0;
            Point3D pnt = Location;
            Map map = Map;

            switch (GetFacing())
            {
                case Direction.North:
                    xOffset = 0; yOffset = -1; break;
                case Direction.South:
                    xOffset = 0; yOffset = 1; break;
                case Direction.West:
                    xOffset = -1; yOffset = 0; break;
                case Direction.East:
                    xOffset = 1; yOffset = 0; break;
            }

            int xo = xOffset;
            int yo = yOffset;
            int lateralOffset = 1;

            while (currentRange++ <= Range)
            {
                xOffset = xo;
                yOffset = yo;

                if (LateralOffset > 1 && currentRange % LateralOffset == 0)
                    lateralOffset++;

                TimeSpan delay = TimeSpan.FromSeconds((double)currentRange / 10.0);

                switch (AmmoType)
                {
                    case AmmunitionType.Empty: break;
                    case AmmunitionType.Cannonball:
                    case AmmunitionType.FrostCannonball:
                    case AmmunitionType.FlameCannonball:
                        {
                            Point3D newPoint = pnt;
                            //List<IEntity> list = new List<IEntity>();

                            for (int i = -lateralOffset; i <= lateralOffset; i++)
                            {
                                if (xOffset == 0)
                                    newPoint = new Point3D(pnt.X + (xOffset + i), pnt.Y + (yOffset * currentRange), pnt.Z);
                                else
                                    newPoint = new Point3D(pnt.X + (xOffset * currentRange), pnt.Y + (yOffset + i), pnt.Z);

                                BaseGalleon g = FindValidBoatTarget(newPoint, map, ammo);

                                if (g != null && g.DamageTaken < DamageLevel.Severely && g.Owner is PlayerMobile)
                                {
                                    var target = new Target();
                                    target.Entity = g;
                                    target.Location = newPoint;
                                    target.Range = currentRange;

                                    return new Target[] { target };
                                }
                            }
                        }
                        break;
                    case AmmunitionType.Grapeshot:
                        {
                            Point3D newPoint = pnt;
                            List<Target> mobiles = new List<Target>();

                            for (int i = -lateralOffset; i <= lateralOffset; i++)
                            {
                                if (xOffset == 0)
                                    newPoint = new Point3D(pnt.X + (xOffset + i), pnt.Y + (yOffset * currentRange), pnt.Z);
                                else
                                    newPoint = new Point3D(pnt.X + (xOffset * currentRange), pnt.Y + (yOffset + i), pnt.Z);

                                foreach (Mobile m in GetTargets(newPoint, map))
                                {
                                    var target = new Target();
                                    target.Entity = m;
                                    target.Location = newPoint;
                                    target.Range = currentRange;

                                    mobiles.Add(target);
                                }

                                if (mobiles.Count > 0 && ammo.SingleTarget)
                                {
                                    var toHit = mobiles[Utility.Random(mobiles.Count)];
                                    ColUtility.Free(mobiles);
                                    return new Target[] { toHit };
                                }
                            }

                            if (mobiles.Count > 0)
                            {
                                return mobiles.ToArray();
                            }
                        }
                        break;
                }
            }

            return null;
        }

        private IEnumerable<Mobile> GetTargets(Point3D newPoint, Map map)
        {
            if (Operator != null)
            {
                foreach (Mobile m in SpellHelper.AcquireIndirectTargets(Operator, newPoint, map, 0).OfType<Mobile>())
                {
                    yield return m;
                }

                yield break;
            }

            IPooledEnumerable eable = map.GetMobilesInRange(newPoint, 0);

            foreach (Mobile m in eable)
            {
                if (m is PlayerMobile || (m is BaseCreature && ((BaseCreature)m).GetMaster() is PlayerMobile))
                {
                    yield return m;
                }
            }
        }

        public struct Target
        {
            public IEntity Entity { get; set; }
            public Point3D Location { get; set; }
            public int Range { get; set; }
        }

        private BaseGalleon FindValidBoatTarget(Point3D newPoint, Map map, AmmoInfo info)
        {
            BaseGalleon galleon = BaseGalleon.FindGalleonAt(newPoint, map);

            if (galleon != null && info.RequiresSurface)
            {
                int d = galleon is BritannianShip ? 3 : 2;
                switch (galleon.Facing)
                {
                    case Direction.North:
                    case Direction.South:
                        if (newPoint.X <= galleon.X - d || newPoint.X >= galleon.X + d)
                            return null;
                        break;
                    case Direction.East:
                    case Direction.West:
                        if (newPoint.Y <= galleon.Y - d || newPoint.Y >= galleon.Y + d)
                            return null;
                        break;
                }

                StaticTile[] tiles = map.Tiles.GetStaticTiles(newPoint.X, newPoint.Y, true);

                foreach (StaticTile tile in tiles)
                {
                    ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
                    bool isWater = (tile.ID >= 0x1796 && tile.ID <= 0x17B2);

                    if (!isWater && id.Surface && !id.Impassable)
                    {
                        return galleon;
                    }
                }

                return null;
            }

            return galleon;
        }

        public virtual void OnShipHit(object obj)
        {
            object[] list = (object[])obj;
            BaseBoat target = list[0] as BaseBoat;
            Point3D pnt = (Point3D)list[1];

            var ammoInfo = AmmoInfo.GetAmmoInfo((AmmunitionType)list[2]);

            if (ammoInfo != null && target != null)
            {
                int damage = (Utility.RandomMinMax(ammoInfo.MinDamage, ammoInfo.MaxDamage));
                damage /= 7;
                target.OnTakenDamage(Operator, damage);

                int z = target.ZSurface;

                if (target.TillerMan != null && target.TillerMan is IEntity)
                {
                    z = ((IEntity)target.TillerMan).Z;
                }

                Direction d = Utility.GetDirection(this, pnt);
                int xOffset = 0;
                int yOffset = 0;
                Point3D hit = pnt;

                if (!ammoInfo.RequiresSurface)
                {
                    switch (d)
                    {
                        default:
                        case Direction.North:
                            xOffset = Utility.RandomMinMax(-1, 1);
                            yOffset = Utility.RandomMinMax(-2, 0);
                            hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z);
                            break;
                        case Direction.South:
                            xOffset = Utility.RandomMinMax(-1, 1);
                            yOffset = Utility.RandomMinMax(0, 2);
                            hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z);
                            break;
                        case Direction.East:
                            xOffset = Utility.RandomMinMax(0, 2);
                            yOffset = Utility.RandomMinMax(-1, 1);
                            hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z);
                            break;
                        case Direction.West:
                            xOffset = Utility.RandomMinMax(-2, 0);
                            yOffset = Utility.RandomMinMax(-1, 1);
                            hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z);
                            break;
                    }
                }

                Effects.SendLocationEffect(hit, target.Map, Utility.RandomBool() ? 14000 : 14013, 15, 10);
                Effects.PlaySound(hit, target.Map, 0x207);

                if (Operator != null && (!Operator.Deleted || CanFireUnmanned))
                {
                    Mobile victim = target.Owner;

                    if (victim != null && target.Contains(victim) && Operator.CanBeHarmful(victim, false))
                    {
                        Operator.DoHarmful(victim);
                    }
                    else
                    {
                        List<Mobile> candidates = new List<Mobile>();
                        SecurityLevel highest = SecurityLevel.Passenger;

                        foreach (var mob in target.GetMobilesOnBoard().OfType<PlayerMobile>().Where(pm => Operator.CanBeHarmful(pm, false)))
                        {
                            if (target is BaseGalleon && ((BaseGalleon)target).GetSecurityLevel(mob) > highest)
                            {
                                candidates.Insert(0, mob);
                            }
                            else
                            {
                                candidates.Add(mob);
                            }
                        }

                        if (candidates.Count > 0)
                        {
                            Operator.DoHarmful(candidates[0]);
                        }
                        else if (victim != null && Operator.IsHarmfulCriminal(victim))
                        {
                            Operator.CriminalAction(false);
                        }

                        ColUtility.Free(candidates);
                    }
                }
            }
        }

        public virtual void OnMobileHit(object obj)
        {
            object[] objects = (object[])obj;
            Mobile toHit = objects[0] as Mobile;
            Point3D pnt = (Point3D)objects[1];

            AmmoInfo ammoInfo = AmmoInfo.GetAmmoInfo((AmmunitionType)objects[2]);

            if (ammoInfo != null)
            {
                int damage = (int)(Utility.RandomMinMax(ammoInfo.MinDamage, ammoInfo.MaxDamage));

                if (Operator != null)
                {
                    Operator.DoHarmful(toHit);
                }

                AOS.Damage(toHit, Operator, damage, ammoInfo.PhysicalDamage, ammoInfo.FireDamage, ammoInfo.ColdDamage, ammoInfo.PoisonDamage, ammoInfo.EnergyDamage);
                Effects.SendLocationEffect(toHit.Location, toHit.Map, Utility.RandomBool() ? 14000 : 14013, 15, 10);
                Effects.PlaySound(toHit.Location, toHit.Map, 0x207);
            }
        }

        public void DoShootEffects()
        {
            Point3D p = Location;
            Map map = Map;

            p.Z -= 3;

            switch (Facing)
            {
                case Direction.North: p.Y--; break;
                case Direction.East: p.X++; break;
                case Direction.South: p.Y++; break;
                case Direction.West: p.X--; break;
            }

            Effects.SendLocationEffect(p, map, 14120, 15, 10);
            Effects.PlaySound(p, map, 0x664);
        }

        public MannedCannon(Serial serial) : base(serial) { }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int)1);

            writer.Write(Operator);
            writer.Write(CanFireUnmanned);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();

            switch (version)
            {
                case 1:
                    Operator = reader.ReadMobile();
                    CanFireUnmanned = reader.ReadBool();
                    break;
            }
        }
    }

    public class MannedCulverin : MannedCannon
    {
        public override int Range { get { return 10; } }
        public override CannonPower Power { get { return CannonPower.Light; } }

        public MannedCulverin(Mobile oper, Direction facing)
            : base(oper, facing)
        {
        }

        public MannedCulverin(Serial serial) : base(serial) { }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int)0);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();
        }
    }

    public class MannedCarronade : MannedCannon
    {
        public override int Range { get { return 10; } }
        public override CannonPower Power { get { return CannonPower.Heavy; } }

        public MannedCarronade(Mobile oper, Direction facing)
            : base(oper, facing)
        {
        }

        public MannedCarronade(Serial serial) : base(serial) { }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int)0);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();
        }
    }

    public class MannedBlundercannon : MannedCannon
    {
        public override int LabelNumber { get { return 1158942; } } // Blundercannon

        public override int Range { get { return 12; } }
        public override CannonPower Power { get { return CannonPower.Massive; } }

        public MannedBlundercannon(Mobile oper, Direction facing)
            : base(oper, facing)
        {
        }

        public MannedBlundercannon(Serial serial) : base(serial) { }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int)0);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();
        }
    }
}
