using Networking.Client;
using Networking.Server;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//public class MapManager : MonoBehaviour
[CreateAssetMenu(menuName = "Major Project/Map Generation/Map Manager")]
public class MapManager : ScriptableObject
{
    public ClientList clients;
    public GameObject spawn;
    public GameObject spawn4; //The section to use as a spawn-point for games with 2-5 players
    public GameObject spawn8; //The section to use a spawn-point for games with 5-8 players
    //In 5-player games, we choose between them at random
    public List<MapSection> sections; //The list of sections to choose from after starting
    //Split up the inspector lists to make them easier to manage. They'll be combined on initialisation
    public List<SectionList> sectionLists;

    public int minConns = 2; //The minimum number of valid connections between two map sections for them to be allowed to link up
    
    public List<MapSection> activeSections; //The list of sections that have been placed on the map (and not removed)
    MapSection lastSection; //Which map-section was most recently added?
    public float startX; //The x-position of the current start of the track
    float startXinit = -16.0f;
    float endX; //The x-position of the current end of the track
    int totalSections; //How many sections have been added? Including ones that have been deleted

    int initialPlayerCount;
    int diffStart = 0; //Initial difficulty rating
    int diffCap = 5; //The highest the difficulty rating can go
    int difficulty; //Current difficulty rating

    /*int diffMin; //Minimum difficulty for a map section to be selected
    int diffMax; //Maximum difficulty for a map section to be selected
    int diffMinStart = 0; //Initial minimum difficulty for a map section to be selected
    int diffMaxStart = 1; //Initial maximum difficulty for a map section to be selected
    int diffMinMax = 4; //The highest diffMin can go - ensure that it doesn't go too high to have legal map sections
    */

    int widthMin; //The minimum widthIn that we want for a new map section
    int widthMax; //The maximum widthIn that we want for a new map section
    int widthMinMin = 3; //The minimum to which widthMin can be reduced
    int widthMaxMin = 5; //The minimum to which widthMax can be reduced

    public void init()
    {
        sections = new List<MapSection>();
        startX = startXinit;
        endX = startX;
        totalSections = 0;

        foreach (SectionList sectionList in sectionLists)
        {
            foreach (MapSection section in sectionList.sectionList)
            {
                sections.Add(section);
            }
        }

        //Debug.Log("sections.Count = " + sections.Count);

        foreach (MapSection section in sections)
        {
            //Debug.Log("Possible section: " + section.name);
        }

        initialPlayerCount = clients.ConnectedClients.Count;
        activeSections = new List<MapSection>();

        if (initialPlayerCount < 5)
        {
            addSection(spawn4.GetComponent<MapSection>());
        }
        else if (initialPlayerCount > 5)
        {
            addSection(spawn8.GetComponent<MapSection>());
        }
        else
        {
            if (Random.Range(0.0f, 1.0f) < 0.5f)
            {
                addSection(spawn4.GetComponent<MapSection>());
            }
            else
            {
                addSection(spawn8.GetComponent<MapSection>());
            }
        }

        widthMin = activeSections[0].widthOut - 2;
        widthMax = activeSections[0].widthOut + 2;

        difficulty = diffStart;

        //Set the maximum possible diffMin value to 1 less than the highest difficulty on the section list
        /*diffMinMax = 0;

        foreach (MapSection section in sections)
        {
            if (section.difficulty > diffMinMax)
            {
                diffMinMax = section.difficulty;
            }
        }

        diffMinMax--;*/

        switch (initialPlayerCount)
        {
            case 2:
                foreach (GameObject spawnBlock in lastSection.spawns2)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 3:
                foreach (GameObject spawnBlock in lastSection.spawns3)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 4:
                foreach (GameObject spawnBlock in lastSection.spawns4)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 5:
                foreach (GameObject spawnBlock in lastSection.spawns5)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 6:
                foreach (GameObject spawnBlock in lastSection.spawns6)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 7:
                foreach (GameObject spawnBlock in lastSection.spawns7)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            case 8:
                foreach (GameObject spawnBlock in lastSection.spawns8)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
            default:
                foreach (GameObject spawnBlock in lastSection.spawns1)
                {
                    spawnBlock.GetComponent<Block>().isSpawnable = true;
                }
                break;
        }

        checkForward();
    }

    void chooseNextSection()
    {
        //First, we determine which sections are valid 
        List<MapSection> validSections = new List<MapSection>();

        updateCriteria(); //We update the section selection criteria for the current gamestate

        foreach (MapSection section in sections)
        {
            if (section.weight > 0 && checkSegments(section))
            {
                //If a segment is a valid continuation of the current most-recent segment, add it to the list
                //Sections with higher weights get more entries => higher chance of being picked
                for (int i = 0; i < section.weight; i++)
                {
                    validSections.Add(section);
                }
            }
        }

        //Having generated our list, we choose a random segment from it

        foreach (MapSection section in validSections)
        {
            //Debug.Log("Valid section: " + section.name);
        }/**/

        //Debug.Log("Choosing section");

        MapSection nextSection = validSections[(int)Random.Range(0.0f, (float)validSections.Count)];

        //Debug.Log("Chosen section: " + nextSection.name);

        addSection(nextSection);
    }

    void addSection(MapSection section)
    {
        //Instantiate new section at x = endX
        Vector3 pos = new Vector3(endX, 0.0f, 0.0f);
        GameObject newSection = (GameObject)Instantiate(section.gameObject, pos, Quaternion.identity);
        //GameObject.Instantiate(section.gameObject, pos, Quaternion.identity);

        MapSection newSectionScript = newSection.GetComponent<MapSection>();

        newSectionScript.InitSection(activeSections.Count);
        newSection.name = newSectionScript.name;
        activeSections.Add(newSectionScript);
        lastSection = newSectionScript;

        endX += newSectionScript.length;
        totalSections++;
    }

    bool checkSegments(MapSection second)
    {
        return checkSegments(this.lastSection, second);
    }

    bool checkSegments(MapSection first, MapSection second)
    {
        int connections = 0;

        //Debug.Log("Checking " + first.name + ", " + second.name);

        //No more than one link section in a row
        if (first.length == 1 && second.length == 1)
        {
            return false;
        }

        //if (second.difficulty < diffMin || second.difficulty > diffMax)
        if (second.difficultyMax < difficulty || second.difficultyMin > difficulty) //Check that we're in the right difficulty range for this section
        {
            //Debug.Log("Difficulty = " + difficulty + ", max = " + second.difficultyMax + ", min = " + second.difficultyMin);
            return false;
        }

        if (second.widthIn < widthMin || second.widthIn > widthMax) //And that it's in the right width range
        {
            //Debug.Log("width = " + second.widthIn + ", max = " + widthMin + ", min = " + widthMax);
            return false;
        }

        ////Debug.Log("Checking sections: first = " + first.name + ", second = " + second.name);

        foreach (GameObject exit in first.exits)
        {
            foreach (GameObject entry in second.entrances)
            {
                //Debug.Log("Checking connections: exit = " + exit.transform.localPosition.z + ", " + exit.transform.localPosition.x+ ", entry = " + entry.transform.localPosition.z + ", " + entry.transform.localPosition.x);/**/
                if (checkConnection(exit, entry))
                {
                    connections++;
                }
            }
        }

        ////Debug.Log("Connections = " + connections);

        if (connections >= minConns)
        {
            ////Debug.Log("Valid section!");
        }
        else
        {
            ////Debug.Log("Invalid section!");
        }

        return (connections >= minConns);
    }

    bool checkConnection(GameObject exit, GameObject entry)
    {
        /*//Debug.Log("Checking connections: exit = " + exit.transform.localPosition.z + ", " + exit.transform.localPosition.x
            + ", entry = " + entry.transform.localPosition.z + ", " + entry.transform.localPosition.x);*/
        //If the squares being checked don't line up, the connection is invalid
        if (exit.transform.localPosition.z != entry.transform.localPosition.z)
        {
            ////Debug.Log(exit.transform.localPosition.z + " != " + entry.transform.localPosition.z);
            return false;
        }

        //If both components require jumping (pits or water), the connection is invalid
        //It's technically possible to cross two water blocks, but we don't count that
        if (requiresJump(exit) && requiresJump(entry))
        {
            ////Debug.Log("Invalid connection - both water");
            return false;
        }

        ////Debug.Log("Exit.is_Walkable = " + exit.GetComponent<Block>().is_Walkable + ", Entry.is_Walkable = " + entry.GetComponent<Block>().is_Walkable);

        //Since we currently don't let people jump over walls, if either block is a wall, the connection is invalid
        if (isWall(exit) || isWall(entry))
        {
            ////Debug.Log("Invalid connection - not walkable");
            return false;
        }

        ////Debug.Log("Exit.isWater = " + exit.GetComponent<Block>().isWater + ", Entry.isWater = " + entry.GetComponent<Block>().isWater);

        ////Debug.Log("Valid connection!");

        //If we've passed all these tests, the connection is valid!
        return true;
    }

    bool requiresJump(GameObject block)
    {
        if (block.GetComponent<Block>() == null) //The object must be a pit trap
        {
            return true;
        }

        return block.GetComponent<Block>().isWater; //If it's not a pit, then whether it requires jumping depends on whether it's water or not
    }

    bool isWall(GameObject block)
    {
        if (block.GetComponent<Block>() == null)
        {
            return true;
        }

        return !(block.GetComponent<Block>().is_Walkable);
    }

    //Check whether it's time to extend the track forward
    void checkForward()
    {
        //We check if the end of the last section of track is in sight
        Vector3 trackEnd = new Vector3(endX, 0.0f); //Get the middle of the end of the last track section

        //If it is, then we add a new section
        if (checkView(trackEnd))
        {
            chooseNextSection();
            spawn.GetComponent<blockSpawn>().wakeup();
            checkForward();
        }
    }

    //Check whether it's time to delete the oldest section of active track
    void checkBack()
    {
        //We check if the end of the first section of track is still in sight
        Vector3 firstSectionEnd = new Vector3(startX + activeSections[0].length, 0.0f); //Get the middle of the end of the first track section

        //If it's not, then we remove it
        if (!(checkView(firstSectionEnd)))
        {
            spawn.GetComponent<blockSpawn>().updatePositions((int)startX + activeSections[0].length);
            startX += activeSections[0].length;
            spawn.GetComponent<blockSpawn>().updatePositions((int)startX);
            activeSections[0].destroySection();
            activeSections.RemoveAt(0);
            
        }
    }

    //Check whether a point is in sight or not
    bool checkView(Vector3 point)
    {
        Vector3 screenPoint = Camera.main.WorldToViewportPoint(point); //Map it into viewport space

        //The camera's field of view is represented by 0 > (x, y) < 1, with z being the distance from the camera
        return (screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1);
    }

    //Checks in both directions for sections needing to be added or removed
    public void checkTrack()
    {
        checkForward();
        checkBack(); 
    }

    //Updates minimum and maximum difficulty, width, etc, based on current gamestate
    public void updateCriteria()
    {
        //Start with base values
        difficulty = diffStart;
        //By default, we can add a section 1 tile wider or narrower on either side than the last section
        widthMin = lastSection.widthOut - 2;
        widthMax = lastSection.widthOut + 2;
        
        /*  Calculate min & max difficulties & width modifications
        *   We recalculate from scratch each time (that is, 
        *   each time a section is added) so as to avoid having
        *   to track which one-off increase has been applied
        *   and which hasn't
        */

        //As the number of players shrinks, we ramp up the difficulty and contract the track
        if (initialPlayerCount > 0)
        {
            if (clients.ConnectedClients.Count <= (float)(0.5f * initialPlayerCount))
            {
                //Debug.Log("Initial players = " + initialPlayerCount + ", current players = " + clients.ConnectedClients.Count + ", player count at half or below");
                difficulty++;

                widthMin -= 2;
                widthMax -= 2;
            }

            if (clients.ConnectedClients.Count <= (float)(0.33f * initialPlayerCount))
            {
                //Debug.Log("Initial players = " + initialPlayerCount + ", current players = " + clients.ConnectedClients.Count + ", player count below 33%");
                difficulty += 2;

                widthMin -= 2;
                widthMax -= 2;
            }
        }

        //Ramp up the difficulty as the track extends
        difficulty += ((int)endX - (int)startX) / 7;

        /*diffMin += (totalSections - 2) / 2;
        diffMax += totalSections / 2;*/

        //Dropping the difficulty-based track narrowing for now - we have wide sections designed to be hard, we want them to show up

        //Once the difficulty has ramped up, we cause the track to steadily narrow
        //It can no longer widen, only remain constant or shrink
        //if (diffMin > 2)
        /*if (difficulty > 2)
        {
            widthMax--;
        }*/

        //Apply caps
        if (difficulty > diffCap)
        {
            difficulty = diffCap;
        }

        if (widthMin < widthMinMin)
        {
            widthMin = widthMinMin;
        }

        if (widthMax < widthMaxMin)
        {
            widthMax = widthMaxMin;
        }
    }

    // Update is called once per frame
    void Update()
    {
        checkTrack();
    }
}
