import { Observable, Subject } from "rxjs";
import { environment } from "../../../environments/environment";
import { MsgBoxService } from "../../services/msg-box.service";
import { ObjectUtils } from "../../utilities/object-utils";
import { GamePlayer } from "../games.model";
import { MD2Clone } from "./factorys/md2-clone";
import { MobSkill } from "./massive-darkness2.model.boss";
import { MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";

const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
export enum MobDlgType {
    Spawn,
    Activating,
    BeenAttacked,
    PreView,
    Dashboard
}
export enum RoundPhase {
    HeroPhase,
    EnemyPhase,
    LevelUpPhase,
    DarknessPhase,
    BossActivation
}
export enum TreasureType {
    Cover,
    Common,
    Rare,
    Epic,
    Legendary
}
export enum HeroClass {
    Berserker,
    Wizard,
    Rogue,
    Ranger,
    Shaman,
    Paladin,
    Druid,
    Necromancer,
    Monk,
    Thinker,
    Bard
}
export enum MobType {
    Mob,
    RoamingMonster,
    Boss
}

export enum MD2Icon {
    Attack,
    Defense,
    Mana,
    Shadow,
    EnemySkill,
    EnemyClaw,
    Reroll,
    Fire,
    Frost,
    OneHand,
    TwoHand,
    Helmet,
    Armor,
    Ring,
    Foot,
    Melee,
    Range,
    Magic,
    HP,
    MP,
    Dice,
    Arrow,
    ArrowBullseye,
    ArrowOverload,
    SoulToken,
    Rage,
    RedDice,
    BlueDice,
    GreenDice,
    YellowDice,
    OrangeDice,
    BlackDice,
    //Below are image based icons
    TreasureToken = 300,
    TreasureToken_Common,
    TreasureToken_Rare,
    TreasureToken_Epic,
    TreasureToken_Legendary,
    HP_Color,
    Mana_Color,
    CorruptToken,
    TimeToken,
    FireToken,
    FrozenToken

}
export enum AttackTarget {
    Random = 40,
    LeastHp = 50,
    LeastMp = 60,
    HighestHp = 70,
    HighestMp = 80,
    LowestLevel = 90,
    MostCorruption = 200,
    LeastCorruption = 201
}
export enum AttackType {
    Melee = 15,
    Range = 16,
    Magic = 17
}
export class AttackInfo {
    constructor(
        type: MD2Icon,
        yellow: number = 0,
        orange: number = 0,
        red: number = 0,
        black: number = 0
    ) {
        this.type = type
        this.orange = orange
        this.red = red
        this.yellow = yellow
        this.black = black
    }
    type: MD2Icon
    orange: number
    red: number
    yellow: number
    black: number
}

export class MD2LevelUpReward {
    constructor(config: Partial<MD2LevelUpReward>) {
        Object.assign(this, config);

    }
    level: number = 1;
    needExp: number = 0;
    currentExp = 0
    extraHp = 0
    extraMp = 0
    extraRareToken = 0
    extraEpicToken = 0
}
export class DrawingBag<T extends IDrawingItem> {
    constructor(drawingItems: IDrawingItem[] = []) {
        this.drawingItems = drawingItems;
        this.removedItems = [];
    }
    drawingItems: IDrawingItem[]
    removedItems: IDrawingItem[]
    public bagIsEmpty(predicate: (value: T) => boolean = undefined) {
        if (predicate) {
            return this.drawingItems.filter(predicate).reduce((sum, current) => sum + current.drawingWeight, 0) == 0;
        } else {
            return this.drawingItems.reduce((sum, current) => sum + current.drawingWeight, 0) == 0;
        }
    }

    public Draw(amount: number): T[] {
        let drawItems: T[] = this.DrawAndRemove(amount);
        this.RestoreRemoveItems();
        return drawItems;
    }
    public DrawAndRemove(amount: number = 1, predicate: (value: T) => boolean = undefined): T[] {
        let drawItems: T[] = [];
        for (let i = 0; i < amount; i++) {
            if (!this.bagIsEmpty(predicate)) {
                let drawItem = null as T;
                let drawingPool = [] as T[];
                if (predicate) {
                    drawingPool = this.drawingItems.filter(predicate) as T[];
                } else {
                    drawingPool = this.drawingItems as T[];
                }
                let drawIndex = Math.random() * drawingPool.reduce((sum, current) => sum + current.drawingWeight, 0);

                let drawCalc = 0;
                for (let i = 0; i < drawingPool.length; i++) {
                    const item = drawingPool[i];
                    drawCalc += item.drawingWeight;
                    if (drawCalc >= drawIndex) {
                        drawItem = MD2Clone.CloneDrawingItem(item);
                        drawItem.drawingWeight = 1;
                        break;
                    }
                }
                //ObjectUtils.CloneValue
                this.RemoveItem(drawItem);
                drawItems.push(drawItem);
            } else {
                break;
            }
        }

        return drawItems;
    }

    public RestoreRemoveItems() {
        for (let i = 0; i < this.removedItems.length; i++) {
            const removedItem = this.removedItems[i];
            this.AddItem(removedItem);
        }
        this.removedItems = [];
    }
    public AddItem(item: IDrawingItem) {
        let existingItem = this.drawingItems.find(i => i.identifyName == item.identifyName);
        if (existingItem) {
            existingItem.drawingWeight += item.drawingWeight;
        } else {
            this.drawingItems.push(item);
        }
    }

    public RemoveItem(item: IDrawingItem) {
        if (item) {

            if (item.identifyName) {
                let existingItem = this.drawingItems.find(i => i.identifyName == item.identifyName);
                if (existingItem) {
                    existingItem.drawingWeight -= item.drawingWeight;

                    let removedItem = this.removedItems.find(i => i.identifyName == item.identifyName);
                    if (removedItem) {
                        removedItem.drawingWeight += item.drawingWeight;
                    } else {
                        this.removedItems.push(item);
                    }
                }
            } else {
                let existingItem = this.drawingItems.find(i => i.name == item.name);
                if (existingItem) {
                    existingItem.drawingWeight -= item.drawingWeight;

                    let removedItem = this.removedItems.find(i => i.name == item.name);
                    if (removedItem) {
                        removedItem.drawingWeight += item.drawingWeight;
                    } else {
                        this.removedItems.push(item);
                    }
                }

            }

        }

    }
    public ClearAllItems() {

        this.drawingItems = [];
        this.removedItems = [];
    }
}
export interface IMobFactory {
    mobName: string;
    generate(level: number): MobInfo;
}
export interface IDrawingItem {
    imageUrl: string
    name: string
    get identifyName(): string
    description: string
    drawingWeight: number
}
export class DrawingItem implements IDrawingItem {
    constructor(
        name: string,
        description: string,
        imageUrl: string,
        drawingWeight: number = 1
    ) {
        this.imageUrl = imageUrl
        this.name = name
        this.description = description
        this.drawingWeight = drawingWeight
    }
    get identifyName(): string {
        return this.name;
    }
    imageUrl: string
    name: string
    description: string
    drawingWeight: number
}
export class TreasureItem extends DrawingItem {
    constructor(type: TreasureType, itemAmount: number = 1) {
        super(`${TreasureType[type]} Treasure`,
            `It's a ${TreasureType[type]} Treasure!`,
            MD2_IMG_URL(`TreasureToken/${TreasureType[type]}.png`), itemAmount);
        this.type = type;
        this.itemAmount = itemAmount;
    }
    type: TreasureType;
    itemAmount: number;
    get identifyName(): string {
        return this.name;
    }
}
export class MobInfo implements IDrawingItem {
    constructor(
        config: Partial<MobInfo> = {}
    ) {
        Object.assign(this, config);
        this.description = config.name;
        this.drawingWeight = 1;
        this.unitRemainHp = config.hp
    }
    type: MobType = MobType.Mob;
    imageUrl: string
    standUrl: string
    leaderImgUrl: string
    minionImgUrl: string
    name: string
    description: string
    drawingWeight: number
    level: number;
    rewardTokens: number;
    hp: number;
    hpPerHero: number;
    mobAmount: number;
    carriedTreasure: TreasureItem[];
    fixedCarriedTreasure: TreasureItem[];
    unitRemainHp: number;
    attackInfos: AttackInfo[];
    defenseInfo: MD2DiceSet;

    skills: MD2MobSkill[];
    actions: number = 0;
    activateDescription: string;

    fireToken: number = 0;
    frozenToken: number = 0;
    corruptionToken: number = 0;
    uiWounds: number;
    uiFireTokens: number;
    uiFrozenTokens: number;
    uiCorruptionTokens: number;
    uiAttackedBy: string;
    extraRule: string;
    get identifyName(): string {
        return `${this.name}_${this.level}`;
    }
    public get carriedTreasureHtml(): string {
        if (!this.carriedTreasure) return '';
        return this.carriedTreasure.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`)
            .concat(this.fixedCarriedTreasure?.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`.repeat(i.drawingWeight))).join();
    }

    public get totalHp(): number {
        switch (this.type) {
            case MobType.Mob:
                return (this.mobAmount - 1) * this.hp + this.unitRemainHp;
            case MobType.RoamingMonster:
                return this.unitRemainHp;
            case MobType.Boss:
            default:
                return this.unitRemainHp;
        }
    }

    public get minionAmount(): number {
        switch (this.type) {
            case MobType.Mob:
                return (this.mobAmount - 1);
            case MobType.RoamingMonster:
            case MobType.Boss:
            default:
                return 0;
        }
    }
    public get leaderExp(): number {
        switch (this.type) {
            case MobType.Mob:
                return 2;
            case MobType.RoamingMonster:
                return 4;
            case MobType.Boss:
            default:
                return 0;
        }
    }
    public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
}
export interface MD2HeroProfile {
    id: string;
    heroClass: HeroClass;
    title: string;
    hp: number;
    mana: number;
    skillHtml: string;
    shadowSkillHtml: string;
}
export class MD2HeroInfo {
    constructor(
        config: Partial<MD2HeroInfo> = {}
    ) {
        Object.assign(this, config);
    }
    class: HeroClass;
    name: string;
    hp: number;
    mp: number;
    ap: number;
    hpMaximum: number;
    mpMaximum: number;
    exp: number = 0;
    level: number = 1;
    fireToken: number = 0;
    frozenToken: number = 0;
    corruptionToken: number = 0;
    playerInfo: GamePlayer;
    imgUrl: string;
    skillHtml: string;
    shadowSkillHtml: string;
    remainActions: number = 3;
    rage: number = 0;
    uiActivating = false;
    uiShowCorruptionToken = false;
    uiBossFight = false;
    uiShowAttackBtn = false;

    public get heroFullName(): string {
        return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
    }

}

export class MD2Rules {
    public static CoreGameLevelBoard = [
        new MD2LevelUpReward({ level: 2, needExp: 5, extraHp: 1, extraMp: 0, extraRareToken: 1 }),
        new MD2LevelUpReward({ level: 3, needExp: 10, extraHp: 1, extraMp: 1, extraEpicToken: 1 }),
        new MD2LevelUpReward({ level: 4, needExp: 12, extraHp: 2, extraMp: 1, extraEpicToken: 1 }),
        new MD2LevelUpReward({ level: 5, needExp: 18, extraHp: 2, extraMp: 2, extraEpicToken: 1 }),
    ];
    public static checkCoreGameLevelup(currentLevel: number, currentExp: number): MD2LevelUpReward {
        let result = null as MD2LevelUpReward;
        let nextLevel = this.CoreGameLevelBoard.find(r => r.level > currentLevel && currentExp >= r.needExp);

        if (nextLevel) {
            result = ObjectUtils.CloneValue(nextLevel) as MD2LevelUpReward;
            result.currentExp = currentExp - nextLevel.needExp;
        }
        return result;
    }
}

export class MD2EnemyPhaseSpecialInfo {
    specialRule: MD2EnemyPhaseSpecialRule
    specialRules = [
        new MD2EnemyPhaseSpecialRule(30, '', ''),
        new MD2EnemyPhaseSpecialRule(44, 'Surprise Attack',
            'One Mob with line of sight to a hero changes its attack profile to ranged during its <b>SECOND</b> action.<br>' +
            'You have to apply this to a Mob that is not in the same zone as a hero, and that could not attack otherwise.<br>' +
            'If no Mob has line of sight to a hero, one Mob moves an additional zone instead.<br>'),
        new MD2EnemyPhaseSpecialRule(22, 'Fast Advance', 'Each Mob moves 1 additional zone in its first move action.<br>' +
            'If a Mob doesn’t move, it adds one additional black enemy die to it’s first attack action instead.<br>' +
            '(If it already has all six black enemy dice in its attack pool, it rerolls one blank result on a black die instead.)<br>'),
        new MD2EnemyPhaseSpecialRule(4, 'A Dark Portal Appears, Guiding Predators To Their Prey',
            'First, move one green portal token from the board to the zone with the hero who has the least amount of health tokens. (If there is no green portal token on the board, put one onto the board.)<br>' +
            'Then, move the Mob that is furthest away from any heroes into this new portal zone. In its following activation, this Mob only has one action.'),
    ]
}
export class MD2EnemyPhaseSpecialRule implements IDrawingItem {
    constructor(drawingWeight: number, title: string, description: string) {
        this.drawingWeight = drawingWeight
        this.title = title
        this.description = description
    }
    get identifyName(): string {
        return this.name;
    }
    imageUrl: string;
    name: string;
    drawingWeight: number;
    title: string
    description: string
}

export interface IDarknessPhaseRule {
    addTreasureToken: Subject<TreasureType>
    spawnMob: Subject<void>
    spawnRoamingMonster: Subject<void>
    runDarknessPhase(): boolean
}
export class CoreGameDarknessPhaseRule implements IDarknessPhaseRule {
    round: number = 1;
    frontEndRound: number = 9;
    extraRound: number = 0;
    constructor() {
    }
    addTreasureToken = new Subject<TreasureType>();
    spawnMob = new Subject<void>();
    spawnRoamingMonster = new Subject<void>();
    runDarknessPhase() {
        if (this.round >= this.frontEndRound) {
            this.extraRound++;
            if (this.extraRound % 4 == 0) {
                this.spawnRoamingMonster.next();
                return false;
            } else if (this.extraRound % 2 == 0) {
                this.spawnMob.next();
                return false;
            }
            return true;
        }
        this.round++;

        switch (this.round) {
            case 3:
            case 9:
                this.spawnRoamingMonster.next();
                return false;
                break;
            case 4:
                this.addTreasureToken.next(TreasureType.Rare);
                break;
            case 5:
            case 7:
                this.spawnMob.next();
                return false;
                break;
            case 6:
            case 8:
                this.addTreasureToken.next(TreasureType.Epic);
                break;

            default:
                break;
        }
        return true;

    }

}

