using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Runtime.Serialization;
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;
using Random = UnityEngine.Random;

[Serializable]
public class Room
{
    #region Static Variables

    private static int instanceCount = 0;
    public static List<RoomType> roomTypes = new List<RoomType>()
    {
        RoomType.Bedroom,
        RoomType.Kitchen,
        RoomType.Bathroom,
        RoomType.LivingRoom
    };

    #endregion
    
    
    #region Variables
    
    public RoomType roomType;
    public int[] size = new[] {0, 0};
    public int[] position;
    public int id;
    public bool visited;

    public List<Furniture> furnitures;
    public List<Furniture> positionedFurnitures;
    public Dictionary<Room, Furniture> linkToDoor;

    [IgnoreDataMember]
    public List<Room> links;
    
    #endregion

    
    #region Constructors

    public Room()
    {
        roomType = GenerateRoomType();
        size = GenerateRoomSize(roomType);
        links = new List<Room>();
        furnitures = new List<Furniture>();
        positionedFurnitures = new List<Furniture>();
        linkToDoor = new Dictionary<Room, Furniture>();
        
        SetId();

        AddFurnitures();
        PositionFurnitures();
    }

    public Room(RoomType roomType)
        : this(roomType, GenerateRoomSize(roomType), new[] {0, 0}) {}

    public Room(Room roomModel)
    {
        roomType = roomModel.roomType;
        size = new[] {roomModel.size[0], roomModel.size[1]};
        position = new[] {roomModel.position[0], roomModel.position[1]};
        
        links = new List<Room>();
        furnitures = new List<Furniture>();
        positionedFurnitures = new List<Furniture>();
        linkToDoor = new Dictionary<Room, Furniture>();
        
        var translationTable = new Dictionary<int, int>();

        foreach (var furniture in roomModel.furnitures)
        {
            var newFurniture = new Furniture(furniture);
            furnitures.Add(newFurniture);

            if (newFurniture.furnitureType == FurnitureType.Door)
            {
                linkToDoor.Add(roomModel.linkToDoor.First(x => x.Value == furniture).Key , newFurniture);
            }
        }
        
        SetId();
    }
    
    public Room(RoomType roomType, int[] size, int[] position)
    {
        this.roomType = roomType;
        this.size = new[] {size[0], size[1]};
        this.position = new[] {position[0], position[1]};
        links = new List<Room>();
        furnitures = new List<Furniture>();
        positionedFurnitures = new List<Furniture>();
        linkToDoor = new Dictionary<Room, Furniture>();
        
        SetId();

        AddFurnitures();
        PositionFurnitures();
    }
    
    private void SetId()
    {
        id = instanceCount;
        instanceCount++;
    }
    
    #endregion

    
    #region Furnitures
    private void AddFurnitures()
    {
        var area = (size[0] - 2) * (size[1] - 4);
        var furnitureCount = Mathf.FloorToInt((float)area / 7);
        var restrictions = RoomRestrictions.GetRoomRestrictions(roomType);
        
        foreach (var furnitureType in restrictions.mustHaveFurniture)
            AddFurniture(new Furniture(furnitureType));

        for (int i = furnitures.Count; i < furnitureCount; i++)
        {
            AddFurniture(new Furniture(roomType));
        }
    }

    public void AddFurniture(Furniture furniture)
    {
        furnitures.Add(furniture);
    }
    
    private void PositionFurnitures()
    {
        Utils.Shuffle(furnitures);
        var toRemove = new List<Furniture>();

        foreach (var furniture in furnitures)
        {
            if (!PositionFurnitureRandomly(furniture))
                toRemove.Add(furniture);
        }

        foreach (var furniture in toRemove)
            furnitures.Remove(furniture);
    }

    public bool PositionFurnitureRandomly(Furniture furniture)
    {
        for (int i = 0; i < 20; i++)
        {
            var position = new int[]
            {
                Random.Range(
                    1, // wall offset(1)
                    size[0] - furniture.size[0] - 1 // size - size offset(1) - wall offset(1) + random exclusive offset(1)
                ),
                Random.Range(
                    1, // wall offset(1)
                    size[1] - furniture.size[1] - 3 // size - size offset(1) - wall offset(3) + random exclusive offset(1)
                )
            };
            
            furniture.SetPosition(position);

            if (!CheckFurnitureCollision(furniture))
            {
                positionedFurnitures.Add(furniture);
                return true;
            }
        }

        return false;
    }
    
    private bool CheckFurnitureCollision(Furniture furniture)
    {
        foreach (var otherFurniture in positionedFurnitures)
        {
            if (furniture != otherFurniture && furniture.Intersects(otherFurniture))
                return true;
        }

        return false;
    }
    
    #endregion

    #region Door

    public void AddDoor(int[] doorGlobalPosition, Sides side, Room link)
    {
        var roomRestriction = RoomRestrictions.GetRoomRestrictions(roomType);
        var furnitureRestriction = ScriptableObject.CreateInstance<FurnitureRestriction>();
        furnitureRestriction.name = "Door";
        furnitureRestriction.width = 1;
        furnitureRestriction.furnitureType = FurnitureType.Door;
        furnitureRestriction.tiles = side == Sides.Bottom
            ? new[,]
            {
                {roomRestriction.door[0]},
                {roomRestriction.door[1]},
            }
            : new[,] {{roomRestriction.floor}};
        furnitureRestriction.tileArray = new Tile[furnitureRestriction.tiles.GetLength(0)];

        var furniture = new Furniture(furnitureRestriction);
        AddFurniture(furniture);
        linkToDoor.Add(link, furniture);
        
        furniture.SetPosition(new []
        {
            doorGlobalPosition[0] - position[0],
            doorGlobalPosition[1] - position[1] - (side == Sides.Bottom ? 2 : 0)
        });
    }

    #endregion
    
    
    #region Positioning
    
    public void SetPosition(int[] newPosition)
    {
        position = newPosition;
    }
    
    public void AddLink(Room newLink)
    {
        links.Add(newLink);
    }
    
    public bool Intersects(Room otherRoom)
    {
        Rect roomRect = new Rect(position[0], position[1], size[0], size[1]);
        Rect otherRoomRect = new Rect(otherRoom.position[0], otherRoom.position[1], otherRoom.size[0], otherRoom.size[1]);

        return roomRect.Overlaps(otherRoomRect);
    }

    public int[] GetEdges() // X1, X2, Y1, Y2
    {
        return new[]
        {
            position[0], position[0] + size[0] - 1, position[1], position[1] + size[1] - 1
        };
    }

    private static RoomType GenerateRoomType()
    {
        var shift = Random.Range(0, 4);

        return (RoomType)(1 << shift);
    }
    
    private static int[] GenerateRoomSize(RoomType targetRoomType)
    {
        var restrictions = RoomRestrictions.GetRoomRestrictions(targetRoomType);

        return new[]
        {
            Random.Range(restrictions.minSize[0], restrictions.maxSize[0] + 1),
            Random.Range(restrictions.minSize[1], restrictions.maxSize[1] + 1),
        };
    }
    
    #endregion
}