import { Injectable } from '@angular/core';
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
import { first, map, reduce } from "rxjs/operators";
import { NbDialogService } from '@nebular/theme';
import { Subject } from 'rxjs';
import { BossMicheal, BossReaper, IBossFight } from '../../games/massive-darkness2/massive-darkness2.model.boss';
import { ADIcon, MessageBoxConfig } from '../../ui/alert-dlg/alert-dlg.model';
import { NumberUtils } from '../../utilities/number-utils';
import { StringUtils } from '../../utilities/string-utils';
import { FileService } from '../file.service';
import { GameRoomService } from '../game-room.service';
import { LoginUserService } from '../login-user.service';
import { MsgBoxService } from '../msg-box.service';
import { SignalRService, SignalRClient, SignalRMessage } from '../signal-r.service';
import { MD2Logic } from '../../games/massive-darkness2/massive-darkness2.logic';
import { MD2InitService } from './md2-init.service';
import { ArrayUtils } from '../../utilities/array-utils';
import { DropDownOption } from '../../entity/dropDownOption';
import { GameBundle, MD2DiceSet, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
import { environment } from '../../../environments/environment';


const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` };
@Injectable({
  providedIn: 'root'
})
export class MD2Service {
  // #region Properties (24)

  private _highestPlayerLevel: number = 1;
  private _playerAmount: number = 2;

  public info: MD2GameInfo;
  public playerHero: MD2HeroInfo;
  public mobInfos: MD2MobInfo[] = [];
  public roamingMobInfos: MD2MobInfo[] = [];
  public mobDeck: DrawingBag<MobInfo>;
  public roamingMobDeck: DrawingBag<MobInfo>;
  public treasureBag: DrawingBag<TreasureItem> = new DrawingBag<TreasureItem>();

  public darknessPhaseRule: IDarknessPhaseRule;
  public enemyPhaseMobs: MobInfo[];
  public enemyPhaseSubject = new Subject<MobInfo>();
  public initialized = false;
  public mobBeenKilledSubject = new Subject<MobInfo>();

  public refreshUI$: Subject<void> = new Subject<void>();
  public refreshTreasureBagSubject = new Subject<DrawingBag<TreasureItem>>();

  public heroAttackingSubject = new Subject<MD2HeroInfo>();


  public get heros() {
    return this.info.heros;
  }
  public get roamingMonsters() {
    return this.info.roamingMonsters;
  }
  public get mobs() {
    return this.info.mobs;
  }
  public get allRoamingMonsterInfos() {
    return this.roamingMobInfos;
  }
  public get allMobInfos() {
    return this.mobInfos;
  }

  // #endregion Properties (24)

  // #region Constructors (1)
  constructor(
    public fileService: FileService,
    public msgBoxService: MsgBoxService,
    public gameRoomService: GameRoomService,
    public loginUserService: LoginUserService,
    public signalRService: SignalRService,
    public dlgService: NbDialogService
  ) {
    this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
    this.info = new MD2GameInfo();
    this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => {
      this.addTreasure(treasureType, 1);
    });
  }

  // #endregion Constructors (1)
  // #region Public Getters And Setters (5)

  public get highestPlayerLevel(): number {
    if (this.heros.length > 0) {
      return Math.max(...this.heros.map(h => h.level));
    } else {
      return 1;
    }
  }

  public get playerAmount(): number {
    if (this.heros.length > 0) {
      return this.heros.length;
    } else {
      return 1;
    }
  }

  public get currentActivateHero(): MD2HeroInfo {
    return this.info.heros.find(h => h.uiActivating);
  }




  // #endregion Public Getters And Setters (5)

  // #region Public Methods (27)


  public addTreasure(type: TreasureType, amount: number = 1) {
    this.treasureBag.AddItem(new TreasureItem(type, amount));
    this.refreshTreasureBagSubject.next(this.treasureBag);
  }

  public darknessPhase() {
    this.heros.forEach(hero => {
      hero.remainActions = 3;
      this.broadcastHeroInfoToOwner(hero);
    });
    this.info.roundPhase = RoundPhase.HeroPhase;
    if (!this.info.isBossFight) {
      this.info.round++;
      if (this.darknessPhaseRule.runDarknessPhase()) {
        this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
      }
    } else {
      this.info.boss.darknessPhase();
      this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO });
    }

    //this.runNextPhase();
  }

  public spawnMob(isRoamingMonster: boolean, mobName: string = null) {
    let mobDeck = null as DrawingBag<MobInfo>;
    let level = 1;
    if (this.highestPlayerLevel < 3) {
    } else if (this.highestPlayerLevel < 5) {
      level = 3;
    } else {
      level = 5;
    }
    if (isRoamingMonster) {
      mobDeck = this.roamingMobDeck;
    } else {
      mobDeck = this.mobDeck;
    }

    if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level)
      .map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) {
      mobDeck.RestoreRemoveItems();
    }

    let newSpawnMob = null as MobInfo;

    if (mobName) {
      newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.name == mobName)[0]);
    } else {
      newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]);
    }

    let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name);

    if (exitingMob) {
      exitingMob.level = newSpawnMob.level;
      exitingMob.hp = newSpawnMob.hp;
      exitingMob.imageUrl = newSpawnMob.imageUrl;
      exitingMob.attackInfos = newSpawnMob.attackInfos;
      exitingMob.defenseInfo = newSpawnMob.defenseInfo;
      exitingMob.skills = newSpawnMob.skills;
    } else {
      if (isRoamingMonster) {
        newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
        this.roamingMonsters.push(newSpawnMob);
      } else {
        newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
        this.mobs.push(newSpawnMob);
      }

      newSpawnMob.carriedTreasure = this.treasureBag.DrawAndRemove(newSpawnMob.rewardTokens);

      this.refreshTreasureBagSubject.next(this.treasureBag);
    }
    newSpawnMob.imageUrl = this.mobImage(newSpawnMob.name, newSpawnMob.level, isRoamingMonster);

    return { exitingMob, mob: newSpawnMob };
  }
  private generateMob(isRoamingMonster: boolean, mobName: string, level: number) {
    let dbMobInfo = isRoamingMonster ? this.allRoamingMonsterInfos.find(m => m.name == mobName) : this.allMobInfos.find(m => m.name == mobName);
    let levelInfo = dbMobInfo.mobLevelInfos.find(l => l.level <= level);
    let skills = dbMobInfo.skills.filter(s => s.level <= level).sort((a, b) => b.seq - a.seq);
    //Distinct by SkillType and Skill Name, take the one with the highest level

    let mobInfo = new MobInfo({
      name: dbMobInfo.name,
      hp: levelInfo.hpPerHero,
      hpPerHero: levelInfo.hpPerHero,
      level: level,
      rewardTokens: levelInfo.rewardTokens,
      defenseInfo: levelInfo.defenceInfo,
      type: dbMobInfo.type,
      skills: []
    });

    for (let i = 0; i < skills.length; i++) {
      const skill = skills[i];
      if (!mobInfo.skills.find(s => s.type == skill.type && s.name == skill.name)) {
        mobInfo.skills.push(skill);
      }
    }
    mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq);
    switch (dbMobInfo.type) {
      case MobType.Mob:
        mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
        mobInfo.minionImgUrl = mobInfo.minionImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
        break;
      case MobType.RoamingMonster:
        mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`);
        mobInfo.minionImgUrl = null;
        break;
      case MobType.Boss:
        mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/Boss/${mobInfo.name}-Stand.png`);
        mobInfo.minionImgUrl = null;
        break;
      default:
        break;
    }
    mobInfo.fixedCarriedTreasure = [];
    levelInfo.fixedRareTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Rare, levelInfo.fixedRareTreasure));
    levelInfo.fixedEpicTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Epic, levelInfo.fixedEpicTreasure));
    levelInfo.fixedLegendTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Legendary, levelInfo.fixedLegendTreasure));


    switch (dbMobInfo.type) {
      case MobType.Mob:
        //Maybe add more mobs later
        mobInfo.mobAmount = this.playerAmount + 1;
        break;
      case MobType.RoamingMonster:
      case MobType.Boss:
        mobInfo.hp = levelInfo.hpPerHero * this.playerAmount;
        mobInfo.unitRemainHp = mobInfo.hp;
        mobInfo.mobAmount = 0;
        mobInfo.attackInfos = [this.getAttackInfo(levelInfo.attackInfo)];
        let altAttackInfo = levelInfo.alterAttackInfo;
        if (altAttackInfo
          && (altAttackInfo.black > 0 || altAttackInfo.blue > 0 || altAttackInfo.green > 0 || altAttackInfo.orange > 0 || altAttackInfo.red > 0 || altAttackInfo.yellow > 0)
        ) {
          mobInfo.attackInfos.push(this.getAttackInfo(altAttackInfo));
        }
        break;
    }
    return mobInfo;
  }
  private getAttackInfo(attackInfo: MD2DiceSet): AttackInfo {
    return new AttackInfo(attackInfo.type as unknown as MD2Icon, attackInfo.yellow, attackInfo.orange, attackInfo.red, attackInfo.black);
  }
  public enemyPhase() {
    //this.msgBoxService
    this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs);

    if (this.enemyPhaseMobs.length > 0) {
      this.enemyPhaseMobs = ArrayUtils.Shuffle(this.enemyPhaseMobs);
      //this.showEnemyPhaseAction();
      this.enemyPhaseSubject.next(this.enemyPhaseMobs[0]);
    } else {
      this.runNextPhase();
    }
  }

  public enterBossFight() {
    this.msgBoxService.showInputbox('Boss Fight', 'Choose the boss', {
      inputType: 'dropdown',
      dropDownOptions: [new DropDownOption('The Reaper', 'The Reaper'), new DropDownOption('Michael - The Corrupted Archangel', 'Michael - The Corrupted Archangel')]
    }).pipe(first()).subscribe(result => {
      if (result) {
        this.info.mobs = [];
        this.info.roamingMonsters = [];

        if (result == 'The Reaper') {
          this.info.boss = new BossReaper(this);
        } else {
          this.info.boss = new BossMicheal(this);
        }
        this.info.roamingMonsters = [];
        this.info.mobs = [];
        this.info.isBossFight = true;
        this.info.isBossFight = true;
        this.info.boss.info.hp = this.info.boss.info.hpPerHero * this.info.heros.length;
        this.info.boss.info.unitRemainHp = this.info.boss.info.hp;
        this.refreshUI$.next();
        this.info.boss.prepareForBossFight();
        this.levelUpPhase(false);
        this.heros.forEach(hero => {
          hero.hp = hero.hpMaximum;
          hero.mp = hero.mpMaximum;
          hero.remainActions = 3;
          hero.uiActivating = false;
          hero.uiBossFight = true;
        });
        this.broadcastGameInfo();
      }

    });
    //this.sendMsgboxMsg
  }
  public activateBoss() {
    this.info.boss.activating();
  }
  public fileList(folderPath: string) {
    return this.fileService.FileList('Images/MD2/' + folderPath);
  }

  public heroFullName(hero: MD2HeroInfo) {
    return MD2Logic.heroFullName(hero);
  }

  public levelUpPhase(runNextPhase: boolean = true) {
    for (let i = 0; i < this.heros.length; i++) {
      const hero = this.heros[i];
      let levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
      while (levelUpInfo != null) {
        hero.level = levelUpInfo.level;
        hero.hp += levelUpInfo.extraHp;
        hero.hpMaximum += levelUpInfo.extraHp;
        hero.mp += levelUpInfo.extraMp;
        hero.mpMaximum += levelUpInfo.extraMp;
        hero.exp = levelUpInfo.currentExp;
        this.broadcastHeroInfoToOwner(hero);
        this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: `Level Up Lv.${hero.level}`, text: 'Please do a skill level up!', icon: ADIcon.INFO });
        levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
      }
    }
    this.refreshUI$.next();
    if (runNextPhase) {
      this.runNextPhase();
    }
  }

  public mobImage(name: string, level: number, isRoamingMonsters: boolean) {
    if (level < 3) {
      level = 1;
    } else if (level < 5) {
      level = 2;
    } else {
      level = 3;
    }
    if (isRoamingMonsters) {
      return this.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
    } else {
      return this.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
    }
  }

  public playerJoin(hero: MD2HeroInfo) {
    hero.playerInfo = this.gameRoomService.currentPlayer();
    hero.level = 1;
    hero.hp = hero.hpMaximum;
    hero.mp = hero.mpMaximum;
    hero.exp = 0;
    this.playerHero = hero;
    // let message = {
    //   receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
    //   from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
    //   actionType: 'hero',
    //   actionName: 'join',

    // } as SignalRMessage;
    // message.parameters = { hero: JSON.stringify(hero) };
    // this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    // });
  }


  public runNextPhase() {
    this.info.roundPhase++;
    switch (this.info.roundPhase) {
      case RoundPhase.HeroPhase:
        this.heros.forEach(hero => {
          hero.remainActions = 3;
          this.broadcastHeroInfoToOwner(hero);
        });
        break;
      case RoundPhase.EnemyPhase:
        this.enemyPhase();
        break;
      case RoundPhase.LevelUpPhase:
        this.levelUpPhase();
        break;
      case RoundPhase.DarknessPhase:
        this.darknessPhase();
        break;
      default: break;
    }
    let parameters = {};
    parameters['phase'] = this.info.roundPhase;
    this.broadcastMessage('roundPhase', '', parameters);
  }

  public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) {
    let message = {
      receiver: { isGroup: false, connectionId: playerSessionId } as SignalRClient,
      from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
      actionType: 'message',
      actionName: 'popup',
    } as SignalRMessage;
    message.parameters = { msg: JSON.stringify(msg) };
    this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    });
  }

  public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) {
    return MD2Logic.getTargetHerosByFilter(this.info.heros, targetType, onlyOne);
  }

  public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
    return MD2Logic.getTargetHerosHtml(beenAttackedHero);
  }

  public drawingHealFountain() {
    let result = new DrawingBag<DrawingItem>([new DrawingItem('Heal Fountain', '3 HP', '', 2), new DrawingItem('Heal Fountain', '4 HP', '', 1), new DrawingItem('Heal Fountain', 'Full HP', '', 1)])
      .Draw(1)[0];
    switch (result.description) {
      case '3 HP':
        this.currentActivateHero.hp = Math.min(this.currentActivateHero.hp + 3, this.currentActivateHero.hpMaximum);
        break;
      case '4 HP':
        this.currentActivateHero.hp = Math.min(this.currentActivateHero.hp + 4, this.currentActivateHero.hpMaximum);
        break;
      case 'Full HP':
        this.currentActivateHero.hp = this.currentActivateHero.hpMaximum;
        break;
      default:
        break;
    }
    this.broadcastHeroInfoToAll(this.currentActivateHero, true);
    //this.broadcastHeroInfoToOwner(this.currentActivateHero);
    this.msgBoxService.show(
      this.imgHtml('/Fountain/Cover.png', '')
      , { text: `${this.currentActivateHero.heroFullName} Recovered ${result.description}.` });
  }

  public openTreasureChest() {
    let result = new DrawingBag<DrawingItem>([
      new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Common)} x 2`, '', 10),
      new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5),
    ])
      .Draw(1)[0];

    this.msgBoxService.show(
      this.imgHtml('/TreasureChest/SmallTresureChest.jpg', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
  }
  public openGreatTreasureChest() {
    let result = new DrawingBag<DrawingItem>([
      new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3),
      new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1),
    ]).Draw(1)[0];

    this.msgBoxService.show(
      this.imgHtml('/TreasureChest/BigTresureChest.png', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
  }

  public iconHtml(icon: MD2Icon, cssClass = '') {


    if (icon == MD2Icon.Fire) {
      cssClass += ' g-color-google-plus ';
    }
    if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
      cssClass += ' g-color-aqua ';
    }
    if (icon < MD2Icon.RedDice) {
      return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
    } else if (icon < MD2Icon.TreasureToken) {
      return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
    } else {
      if (!cssClass) {
        cssClass = 'g-height-25 mr-1';
      }
      //image based icons
      switch (icon) {

        case MD2Icon.HP_Color:
          return this.imgHtml('HeartIcon.png', cssClass);
        case MD2Icon.Mana_Color:
          return this.imgHtml('ManaIcon.png', cssClass);
        case MD2Icon.CorruptToken:
          return this.imgHtml('Tokens/CorruptToken.png', cssClass);
        case MD2Icon.TimeToken:
          return this.imgHtml('Tokens/TimeToken.png', cssClass);
        case MD2Icon.FireToken:
          return this.imgHtml('Tokens/FireToken.png', cssClass);
        case MD2Icon.FrozenToken:
          return this.imgHtml('Tokens/FrozenToken.png', cssClass);
        case MD2Icon.TreasureToken:
          return this.imgHtml('TreasureToken/Cover.png', cssClass);
        case MD2Icon.TreasureToken_Common:
          return this.imgHtml('TreasureToken/Common.png', cssClass);
        case MD2Icon.TreasureToken_Rare:
          return this.imgHtml('TreasureToken/Rare.png', cssClass);
        case MD2Icon.TreasureToken_Epic:
          return this.imgHtml('TreasureToken/Epic.png', cssClass);
        case MD2Icon.TreasureToken_Legendary:
          return this.imgHtml('TreasureToken/Legendary.png', cssClass);
      }
    }
  }

  public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1') {
    return `<img src='${this.imgUrl(imgPath)}' class='${cssClass}'>`;
  }

  public imgUrl(imgPath: string) {
    return this.fileService.ImageUrl('MD2/' + imgPath);
  }
  public treasureImage(type: TreasureType) {
    return this.imgUrl(`TreasureToken/${TreasureType[type]}.png`);
  }
  public treasureImageHtml(type: TreasureType) {
    return this.imgHtml(`TreasureToken/${TreasureType[type]}.png`, 'g-height-40 mr-1');
  }

  public broadcastAllHeroInfoToAll() {
    let message = {
      receiver: { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient,
      from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
      actionType: 'heroes',
      actionName: 'updateAll',
    } as SignalRMessage;
    message.parameters = { heros: JSON.stringify(this.info.heros) };
    this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    });
    this.broadcastMobsInfo();
  }

  public broadcastHeroAction(action: string, extraValue: any = null) {
    let parameters = {};
    parameters['tabId'] = this.loginUserService.sessionTabId;
    parameters['extraValue'] = JSON.stringify(extraValue);
    this.broadcastMessage('heroAction', action, parameters);
  }

  public broadcastHeroInfoToAll(hero: MD2HeroInfo, fromDashboard: boolean = false) {
    let message = {
      receiver: { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient,
      from: { isGroup: fromDashboard, connectionId: fromDashboard ? this.gameRoomService.gameRoomId : hero.playerInfo.signalRClientId },
      actionType: 'hero',
      actionName: 'update',
    } as SignalRMessage;
    message.parameters = { hero: JSON.stringify(hero) };
    this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    });
  }

  public broadcastHeroInfoToOwner(hero: MD2HeroInfo) {
    let message = {
      receiver: { isGroup: false, connectionId: hero.playerInfo.signalRClientId } as SignalRClient,
      from: { isGroup: true, connectionId: this.gameRoomService.gameRoomId },
      actionType: 'hero',
      actionName: 'updateMyHero',
    } as SignalRMessage;
    message.parameters = { hero: JSON.stringify(hero) };
    this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    });
  }

  /**
   * `sessionId` = null means broadcast to all
   */
  public broadcastMessage(actionType: string,
    actionName: string,
    parameters: { [key: string]: string; } = {},
    sessionId: string = null) {
    let message = {
      receiver: { isGroup: !sessionId, connectionId: sessionId } as SignalRClient,
      from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
      actionType: actionType,
      actionName: actionName,
      parameters: parameters
    } as SignalRMessage;

    if (sessionId == null) {
      message.receiver = { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
    } else {
      message.receiver = { isGroup: false, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
    }
    this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
    });
  }
  public broadcastGameInfo() {
    let parameters = {};
    if (this.info.boss) {
      this.info.boss.md2Service = undefined;
    }
    parameters['gameInfo'] = JSON.stringify(this.info);

    if (this.info.boss) {
      this.info.boss.md2Service = this;
    }
    this.broadcastMessage('GameRoom', 'update', parameters);
  }
  broadcastFetchGameInfo() {
    this.broadcastMessage('GameRoom', 'getGameInfo', {});
  }
  public broadcastRoundPhase() {
    let parameters = {};
    parameters['phase'] = JSON.stringify(this.info.roundPhase);
    this.broadcastMessage('GameRoom', 'phase', parameters);
  }

  public broadcastMobsInfo() {
    let parameters = {};
    parameters['roamingMonsters'] = JSON.stringify(this.info.roamingMonsters);
    parameters['mobs'] = JSON.stringify(this.info.mobs);
    this.broadcastMessage('mobs', 'update', parameters);
  }

  public broadcastMyHeroInfo() {
    this.broadcastHeroInfoToAll(this.playerHero);
  }


  // #endregion Public Methods (27)

}

export class MD2GameInfo {
  /**
   *
   */
  constructor(
    config: Partial<MD2GameInfo> = undefined
  ) {
    if (config) {
      Object.assign(this, config);

      this.mobs = this.mobs.map(m => new MobInfo(m));
      this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m));
      if (this.boss && this.boss.info) {
        this.boss.info = new MobInfo(this.boss.info);
      }
      this.heros = this.heros.map(h => new MD2HeroInfo(h));
    }

  }
  public isBossFight: boolean = false;
  public mobs: MobInfo[] = [];
  public roamingMonsters: MobInfo[] = [];
  public heros: MD2HeroInfo[] = [];
  public disconnectedHeroes: MD2HeroInfo[] = [];
  public round = 1;
  public roundPhase: RoundPhase = RoundPhase.HeroPhase;
  public showAttackBtn: boolean = false;
  public boss: IBossFight;
}