import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame, Layout, UITransform } from 'cc';
import { CardMatchData, CardTypes, UnlockData } from '../../data/DataModel';
import { UIManager } from '../../global/ui/UIManager';
import { LevelManager } from '../../global/data-flow/LevelManager';
import { GAME_STATE, GameManager } from '../../global/state/GameManager';
import { Card } from '../../../components/card/base/Card';
import { EventArgs, EventOperations, EventsType, IShowCardsHandler } from '../../global/interface/IPrimary';
import { Cards_Constants } from '../../../constants/Cards_Constants';
import { CardClipType, SoundManager } from '../../global/Sound/SoundManager';
import { ManagerType } from '../../global/data-flow/TopLevelManager';

const { ccclass, property } = _decorator;

@ccclass('CardData')
export class CardViewData 
{
    @property({type: SpriteFrame})
    public m_SpriteFrame: SpriteFrame;

    @property({type: CardMatchData})
    public m_CardMatchData: CardMatchData;
}

@ccclass('CardManager')
export class CardManager extends Component implements IShowCardsHandler
{
    @property({type: UITransform})
    public m_Canvas: UITransform;

    @property({type: UITransform})
    public m_Root: UITransform;
    
    @property({type: [CardViewData]})
    public m_CardDatas: CardViewData[] = [];
    
    @property({type: Node})
    public m_CardPanel: Node;
    
    @property({type: Prefab})
    public m_CardPrefab: Prefab;

    @property({type: Prefab})
    public m_CardPlaceholder: Prefab;

    @property({type: Number})
    public m_MatchTimeInterval: number;

    @property({type: Number})
    public m_MaxMatches: number = 2;
    
    @property({type: Number})
    public m_MaxTurnsForHint: number = 3;

    @property({type: Layout})
    public m_Layout: Layout; 

    private gameManager: GameManager;
    private soundManager: SoundManager;
    private m_LevelManager: LevelManager;
    private m_UIManager: UIManager;

    private cards: Card[][];
    // private cachedArray: CachedArray<CardData>;
    private m_MutableCardMatchData: CardMatchData[] = [];
    private m_CachedCardMatchData: CardMatchData[] = [];
    private m_UnlockData: UnlockData;

    private m_FirstSelectedCard: Card = null;
    private m_SecondSelectedCard: Card = null;
    
    private canFlip: boolean = false;

    private m_Rows: number;
    private m_Cols: number;
    private m_TotalPairs: number;
    private m_PairsCounter: number;
    private m_CardsLeft: number;
    private m_TurnsCount: number = 0;
    private m_HintCounter: number = 0;
    private m_MatchesCount: number = 0;

    private evtArgs: EventArgs;

    public get CanFlip() : boolean
    {
        return this.canFlip;
    }

    public get GetTotalPairs()
    {
        return this.m_TotalPairs;
    }

    protected onLoad(): void 
    {
        this.evtArgs = new EventArgs();
    }

    public fn_Execute(evtType: EventsType): void 
    {
        switch (evtType)
        {
            case EventsType.ARCHIVE_CARDS:
                this.fn_ArchiveCards();    
            break;
            case EventsType.HINT_BAR: 
                this.fn_OnHintRequest();
            break;
        }
    }

    public fn_ArchiveCards(): void 
    {
        this.m_FirstSelectedCard = this.m_SecondSelectedCard = null;
        this.fn_SaveCardData();
        this.fn_RemoveAllCards();
    }

    public fn_OnHintRequest(): void 
    {
        this.fn_ShowAllCardsFirstTime();
    }

    public fn_GetMatchesCount() : number 
    {
        return this.m_MatchesCount;
    }

    public fn_GetTurnsCount() : number 
    {
        return this.m_TurnsCount;
    }

    fn_InitGridInfo(m_Rows: number, m_Cols: number) 
    {
        this.m_Rows = m_Rows;
        this.m_Cols = m_Cols;

        this.m_TotalPairs = this.m_PairsCounter = (this.m_Rows * this.m_Cols) / 2;
        this.m_CardsLeft = this.m_TotalPairs * 2;

        this.m_MutableCardMatchData = new Array(this.m_PairsCounter);
        this.m_CachedCardMatchData = new Array(this.m_PairsCounter * 2);

        for (let index = 0; index < this.m_PairsCounter; index++)
        {            
            this.m_CardDatas[index].m_CardMatchData.m_MatchCounter = 0;
            this.m_MutableCardMatchData[index] = this.m_CardDatas[index].m_CardMatchData;
        }

        this.fn_InitHUDInfo();
        this.fn_UpdateHUD();
    }
    
    fn_Init(gameManager: GameManager)
    {
        this.gameManager = gameManager;
        this.m_LevelManager = gameManager.m_LevelManager;
        this.m_UIManager = gameManager.m_UIManager;
        this.m_MatchesCount = this.m_TurnsCount = this.m_HintCounter = 0;
        this.canFlip = false;
    }

    fn_InitSelectedCard(selectedCard: Card)
    {
        if (!this.canFlip)
        {       
            return;
        }

        /** GAME OVER CASE */
        

        this.m_UIManager.fn_HideHintHUD();
        if (!this.m_FirstSelectedCard)
        {
            this.m_FirstSelectedCard = selectedCard;
        }
        else if (!this.m_SecondSelectedCard)
        {
            this.m_SecondSelectedCard = selectedCard;
            this.canFlip = false;
        }
            
        this.fn_PlayClip(CardClipType.FLIP);
        selectedCard.m_CardRotator.fn_TriggerRotation(true);
        if (this.m_FirstSelectedCard && this.m_SecondSelectedCard)
        {
            this.fn_CheckForMatch();
        }
    }

    private CheckGameOverCondition() : boolean
    {
        if (this.m_TurnsCount >= this.m_TotalPairs * 4) // logic needs to be updated
        {
            this.canFlip = false;
            this.gameManager.fn_OnGameOver();

            return true;
        }

        return false;
    }

    private fn_PlayClip(type: CardClipType)
    {
        this.soundManager = this.soundManager == null ? 
                            this.gameManager.mainManager.fn_GetMainManager<SoundManager>(ManagerType.SOUND) : 
                            this.soundManager;

        this.soundManager.fn_PlayClip(type);
    }

    private fn_CheckForMatch()
    {
        this.m_TurnsCount++;
        if (this.m_FirstSelectedCard.m_CardType == this.m_SecondSelectedCard.m_CardType)
        {
            let matchDatas = this.m_CachedCardMatchData.filter(data => data.m_Type == this.m_FirstSelectedCard.m_CardType);
            matchDatas.forEach(data => data.m_MatchCounter = this.m_MaxMatches);
            this.m_CardsLeft -= this.m_MaxMatches;
            
            setTimeout(() => {
                this.fn_PlayClip(CardClipType.MATCH);
                this.m_FirstSelectedCard.fn_OnMatchFound();
                this.m_SecondSelectedCard.fn_OnMatchFound();
                this.fn_ResetInfo();
            }, Cards_Constants.MILLI_SEC * this.m_MatchTimeInterval);
            
            this.m_MatchesCount++;
            this.m_HintCounter = 0;

            this.fn_SaveCardData();
        }
        else 
        {
            this.fn_PlayClip(CardClipType.NO_MATCH);
            if (!this.CheckGameOverCondition())
            {
                this.m_HintCounter++;

                setTimeout(() => {
                    this.fn_PlayClip(CardClipType.UNFLIP);

                    this.m_FirstSelectedCard.m_CardRotator.fn_TriggerRotation(false);
                    this.m_SecondSelectedCard.m_CardRotator.fn_TriggerRotation(false);

                    this.fn_CheckForHint();
                    this.fn_ResetInfo();

                }, Cards_Constants.MILLI_SEC * this.m_MatchTimeInterval);
            }
            else 
            {
                this.fn_ResetInfo();
            }
        }

        if (this.m_MatchesCount == this.m_TotalPairs)
        {
            this.gameManager.fn_OnLevelCompleted();
        }

        this.fn_UpdateHUD();
    }

    private fn_CheckForHint()
    {
        this.m_UIManager.fn_ShowHintHUD(this.m_HintCounter === this.m_MaxTurnsForHint);
        if (this.m_HintCounter === this.m_MaxTurnsForHint)
            this.m_HintCounter = 0;
    }

    private fn_InitHUDInfo()
    {
        this.m_UIManager.fn_InitHUDInfo(this.m_TotalPairs, this.m_TotalPairs * 4); // for tries logic is totalCards * 2 for now
    }

    private fn_UpdateHUD()
    {
        if (!this.m_UIManager) return;

        this.m_UIManager.fn_UpdateMatchesHUD(this.m_MatchesCount);
        this.m_UIManager.fn_UpdateTurnsHUD(this.m_TurnsCount);
    }

    private fn_ResetInfo() 
    {
        this.m_FirstSelectedCard = this.m_SecondSelectedCard = null;
        this.canFlip = true;
    }

    public fn_CreateCardsAndSetData(rows: number, cols: number) 
    {
        this.fn_RemoveAllCards();

        this.cards = [];
        this.m_UnlockData = this.m_LevelManager.fn_IsLevelUnfinished(); 

        if (this.m_UnlockData != null)
        {
            // Update the turns count
            this.m_CardsLeft = this.m_UnlockData.cardsLeft;
            
            this.fn_LoadSavedData();
        }
        else 
        {
            this.LoadFreshCardsData(rows, cols);
        }
    }

    private fn_LoadSavedData()
    {
        // this.m_PairsCounter
        this.m_TurnsCount = this.m_UnlockData.turnsCount;

        this.m_MatchesCount = Math.abs(this.m_UnlockData.cardsMatchData.length - this.m_UnlockData.cardsLeft) / 2;
        this.fn_UpdateHUD();
        this.LoadSavedCardsData();
    }

    private LoadSavedCardsData() 
    {
        let indexer = 0;
        for (let i = 0; i < this.m_Rows; i++)
        {
            this.cards[i] = [];
            for (let j = 0; j < this.m_Cols; j++) 
            {
                let cardMatchData = this.m_UnlockData.cardsMatchData[indexer];
                let cardInst = instantiate(cardMatchData.m_MatchCounter === this.m_MaxMatches ?
                    this.m_CardPlaceholder : this.m_CardPrefab);
                this.m_CardPanel.addChild(cardInst);
                let card = cardInst.getComponent(Card);

                // accessing sprite frame based on index
                card.fn_Init(cardMatchData.m_Type, this.m_CardDatas[cardMatchData.m_Type].m_SpriteFrame, this);
                indexer = this.fn_InitCachedCardMatchData(indexer, cardMatchData.m_Type, cardMatchData.m_MatchCounter);

                this.cards[i][j] = card;
            }
        }
    }

    private LoadFreshCardsData(rows: number, cols: number) 
    {
        let indexCounter = 0;
        for (let i = 0; i < rows; i++)
        {
            this.cards[i] = [];
            for (let j = 0; j < cols; j++)
            {
                let cardInst = instantiate(this.m_CardPrefab);
                this.m_CardPanel.addChild(cardInst);

                let card = cardInst.getComponent(Card);
                let cardMatchData = this.fn_FindSpecificCardData();

                indexCounter = this.fn_InitCachedCardMatchData(indexCounter, cardMatchData.m_Type, 0);

                card.fn_Init(cardMatchData.m_Type, this.m_CardDatas[cardMatchData.m_Type].m_SpriteFrame, this);
                this.cards[i][j] = card;
            }    
        }
    }

    private fn_InitCachedCardMatchData(indexCounter: number, cardType: CardTypes, matchCounter: number) 
    {
        this.m_CachedCardMatchData[indexCounter] = new CardMatchData();
        this.m_CachedCardMatchData[indexCounter].m_Type = cardType;
        this.m_CachedCardMatchData[indexCounter].m_MatchCounter = matchCounter;
        indexCounter++;

        return indexCounter;
    }

    public fn_RemoveAllCards() {

        this.m_CardPanel.removeAllChildren();

        this.m_CardPanel.children.forEach(child => {
            child.destroy();
        });
    }

    public fn_ShowAllCardsFirstTime()
    {
        for (let i = 0; i < this.m_Rows; i++)
        {
            for (let j = 0; j < this.m_Cols; j++)
            {
                if (this.cards[i][j] && this.cards[i][j].node.active)
                {
                    this.cards[i][j].fn_flipCard();
                }
            }
        }

        // this.fn_PlayClip(ClipType.SHOW_ALL);
        setTimeout(() => {
            this.canFlip = true;
        }, this.cards[0][0].m_CardFlipInterval * Cards_Constants.MILLI_SEC);
    }

    public fn_DebugShowAllCards()
    {
        this.canFlip = true;

        for (let i = 0; i < this.m_Rows; i++)
        {
            for (let j = 0; j < this.m_Cols; j++)
            {
                if (this.cards[i][j])
                {
                    this.cards[i][j].fn_DisplayCard();
                }
            }
        }
    }

    public fn_DebugHideAllCards()
    {
        for (let i = 0; i < this.m_Rows; i++)
        {
            for (let j = 0; j < this.m_Cols; j++)
            {
                if (this.cards[i][j])
                {
                    this.cards[i][j].fn_HideCard();
                }
            }
        }

        setTimeout(() => {
            this.canFlip = false;
        }, this.cards[0][0].m_CardFlipInterval * Cards_Constants.MILLI_SEC);
    }

    private fn_FindSpecificCardData()
    {
        let index = (Math.floor(Math.random() * this.m_PairsCounter));
        let cardMatchData = this.m_MutableCardMatchData[index];
        
        this.m_MutableCardMatchData[index].m_MatchCounter++;

        if (this.m_MutableCardMatchData[index].m_MatchCounter === this.m_MaxMatches)
        {
            // this.fn_RemoveAt(index);
            this.m_MutableCardMatchData.splice(index, 1);
            this.m_PairsCounter--;
        }

        return cardMatchData;
    }

    public fn_SaveCardData(forceSave: boolean = false) 
    {
        console.log("Save card data");
        if (forceSave || this.m_CardsLeft == 0 || this.m_TurnsCount >= this.m_TotalPairs * this.m_TotalPairs)
        {
            this.m_LevelManager.fn_SetUnlockLevelData(null);
            return;
        }

        if (this.m_UnlockData === null)
            this.m_UnlockData = new UnlockData();

        this.m_UnlockData.turnsCount = this.m_TurnsCount;

        this.m_UnlockData.cardsLeft = this.m_CardsLeft;

        this.m_UnlockData.cardsMatchData = this.m_CachedCardMatchData;
        this.m_LevelManager.fn_SetUnlockLevelData(this.m_UnlockData);
    }
}

