/// <summary>
    /// スタミナを消費する
    /// </summary>
    public static async Task <int> ConsumeAsync(FunctionExecutionContext <dynamic> context, int consumeStamina)
    {
        var response = await CalculateAndSetCurrentStaminaAsync(context);

        PMApiUtil.ErrorIf(response.currentStamina < consumeStamina, PMErrorCode.Unknown, "not enough stamina.", new Dictionary <string, object>()
        {
            { "currentStamina", response.currentStamina },
            { "consumeStamina", consumeStamina },
        });

        var remainStamina = response.currentStamina - consumeStamina;
        // スタミナ消費時は時間の更新は行わない
        await DataProcessor.UpdateUserDataAsync(context, new Dictionary <UserDataKey, object>(){
            { UserDataKey.stamina, remainStamina }
        });

        return(remainStamina);
    }
        public static async Task <dynamic> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            try{
                string body = await req.ReadAsStringAsync();

                var context = JsonConvert.DeserializeObject <FunctionExecutionContext <dynamic> >(body);
                var request = JsonConvert.DeserializeObject <FunctionExecutionContext <MonsterLevelUpApiRequest> >(body).FunctionArgument;

                // 対象のモンスターを取得
                var userInventory = await DataProcessor.GetUserInventoryAsync(context);

                var userMonster = userInventory.userMonsterList.FirstOrDefault(u => u.id == request.userMonsterId);
                PMApiUtil.ErrorIf(userMonster == null, PMErrorCode.Unknown, "invalid userMonsterId");

                // 経験値を十分に保持しているかチェック
                var exp = userInventory.userPropertyList.FirstOrDefault(u => u.propertyId == (long)PropertyType.MonsterExp);
                PMApiUtil.ErrorIf(exp == null || exp.num < request.exp, PMErrorCode.Unknown, "not enough exp");

                // 何レベになるか計算
                var levelUpTableList = await DataProcessor.GetMasterAsyncOf <MonsterLevelUpTableMB>(context);

                var afterExp           = userMonster.customData.exp + request.exp;
                var targetLevelUpTable = levelUpTableList.OrderBy(m => m.id).LastOrDefault(m => m.totalRequiredExp <= afterExp);
                PMApiUtil.ErrorIf(targetLevelUpTable == null, PMErrorCode.Unknown, "invalid levelUpTable");
                var afterLevel = targetLevelUpTable.level;

                // 対象のモンスターがマスタに存在するかチェック
                var monsterList = await DataProcessor.GetMasterAsyncOf <MonsterMB>(context);

                var monster = monsterList.FirstOrDefault(m => m.id == userMonster.monsterId);
                PMApiUtil.ErrorIf(monster == null, PMErrorCode.Unknown, "invalie monsterId");

                // モンスターをレベルアップ
                var afterStatus = MonsterUtil.GetMonsterStatus(monster, afterLevel);
                var customData  = new UserMonsterCustomData()
                {
                    level  = afterLevel,
                    exp    = afterExp,
                    hp     = afterStatus.hp,
                    attack = afterStatus.attack,
                    heal   = afterStatus.heal,
                    grade  = userMonster.customData.grade,
                };
                await DataProcessor.UpdateUserMonsterCustomDataAsync(context, userMonster.id, customData);

                // 経験値を消費
                await DataProcessor.ConsumeItemAsync(context, exp.id, request.exp);

                // 強化後のレベルを返す
                var response = new MonsterLevelUpApiResponse()
                {
                    level = afterLevel
                };
                return(PlayFabSimpleJson.SerializeObject(response));
            }catch (PMApiException e) {
                // レスポンスの作成
                var response = new PMApiResponseBase()
                {
                    errorCode = e.errorCode,
                    message   = e.message
                };
                return(PlayFabSimpleJson.SerializeObject(response));
            }
        }
    /// <summary>
    /// 時間経過によるスタミナ回復を考慮した現在のスタミナを計算し保存し返す
    /// </summary>
    private static async Task <CalculateStaminaResult> CalculateAndSetCurrentStaminaAsync(FunctionExecutionContext <dynamic> context)
    {
        var userData = await DataProcessor.GetUserDataAsync(context);

        var staminaList = await DataProcessor.GetMasterAsyncOf <StaminaMB>(context);

        var lastCalculatedStaminaDateTime = userData.lastCalculatedStaminaDateTime;
        var now = DateTimeUtil.Now();

        if (lastCalculatedStaminaDateTime == default(DateTime))
        {
            // 初ログイン時
            var rank    = 1; // 初ログイン時はランク1としている
            var stamina = staminaList.First(m => m.rank == rank).stamina;
            await DataProcessor.UpdateUserDataAsync(context, new Dictionary <UserDataKey, object>(){
                { UserDataKey.lastCalculatedStaminaDateTime, now },
                { UserDataKey.stamina, stamina }
            });

            return(new CalculateStaminaResult()
            {
                currentStamina = stamina,
                lastCalculatedDateTime = now,
            });
        }
        else
        {
            var staminaMB = staminaList.FirstOrDefault(m => m.rank == userData.rank);
            PMApiUtil.ErrorIf(staminaMB == null, PMErrorCode.Unknown, $"invalid user rank => userRank:{userData.rank}");

            var maxStamina     = staminaMB.stamina;
            var stamina        = userData.stamina;
            var currentStamina = 0;
            var newLastCalculatedStaminaDateTime = new DateTime();

            if (stamina >= maxStamina)
            {
                // すでに最大スタミナ値を超えている場合は時間経過でのスタミナを追加しない
                currentStamina = stamina;
                newLastCalculatedStaminaDateTime = now;
            }
            else
            {
                // 時間経過による回復スタミナを追加する
                var span = now - lastCalculatedStaminaDateTime;
                var totalMilliSeconds  = span.TotalMilliseconds;
                var increasedStamina   = (int)Math.Floor(totalMilliSeconds / ConstManager.User.millSecondsPerStamina);     // 経過時間を間隔で割った商が回復したスタミナ
                var remainMilliSeconds = totalMilliSeconds - (increasedStamina * ConstManager.User.millSecondsPerStamina); // 今のスタミナになってから経過した時間
                currentStamina = Math.Min(stamina + increasedStamina, maxStamina);
                newLastCalculatedStaminaDateTime = now.AddMilliseconds(-remainMilliSeconds);                               // lastCalculatedStaminaDateTimeには今のスタミナになったちょうどの日時を登録する
            }

            await DataProcessor.UpdateUserDataAsync(context, new Dictionary <UserDataKey, object>(){
                { UserDataKey.lastCalculatedStaminaDateTime, newLastCalculatedStaminaDateTime },
                { UserDataKey.stamina, currentStamina }
            });

            return(new CalculateStaminaResult()
            {
                currentStamina = currentStamina,
                lastCalculatedDateTime = newLastCalculatedStaminaDateTime,
            });
        }
    }