# Path: modules/game/EngineEval.py
# class EngineEval(NamedTuple('EngineEval', [
#         ('cp', Opt[Number]),
#         ('mate', Opt[int])
#     ])):
#     @staticmethod
#     def fromDict(d: Dict):
#         return EngineEval(d.get('cp', None), d.get('mate', None))
# 
#     def asdict(self) -> Dict:
#         return {'cp': self.cp} if self.cp is not None else {'mate': self.mate}
# 
#     def inverse(self):
#         return EngineEval(-self.cp if self.cp is not None else None,
#             -self.mate if self.mate is not None else None)
# 
#     def winningChances(self, colour: Colour) -> Number:
#         if self.mate is not None:
#             base = (1 if self.mate > 0 else 0)
#         else:
#             base = 1 / (1 + exp(-0.004 * self.cp))
#         return 100*(base if colour else (1-base))
# 
# class EngineEvalBSONHandler:
#     @staticmethod
#     def reads(bson: Dict) -> List[EngineEval]:
#         return EngineEval.fromDict(bson)
# 
#     def writes(engineEval: EngineEval) -> Dict:
#         return engineEval.asdict()
# 
# Path: modules/game/Game.py
# class Game(NamedTuple('Game', [
#         ('id', GameID),
#         ('white', PlayerID),
#         ('black', PlayerID),
#         ('pgn', List[str]),
#         ('emts', Opt[List[Emt]]),
#         ('analysis', Analysis)
#     ])):
# class GameBSONHandler:
# class GameDB(NamedTuple('GameDB', [
#         ('gameColl', Collection)
#     ])):
#     def fromDict(d: Dict):
#     def fromJson(json: Dict):
#     def toJson(self):
#     def playable(self):
#     def boardTensors(self, colour):
#     def boardTensorsByPlayerId(self, playerId: PlayerID, length: int = 60, safe: bool = True):
#     def tensor(self, playerId: PlayerID, length: int = 60, noisey: bool = False, safe: bool = True) -> Opt[GameTensor]:
#     def emtsByColour(self, colour: Colour, emts: Opt[List[int]] = None) -> List[Emt]:
#     def moveTensor(analysis: Analysis, emt: Emt, boardTensor: List[int], avgEmt: Number, colour: Colour) -> MoveTensor:
#     def nullBoardTensor():
#     def nullMoveTensor() -> MoveTensor:
#     def ply(moveNumber: int, colour: Colour) -> int:
#     def reads(bson: Dict) -> Game:
#     def writes(game: Game) -> Dict:
#     def byId(self, _id: GameID) -> Opt[Game]:
#     def byIds(self, ids: List[GameID]) -> List[Game]:
#     def byPlayerId(self, playerId: PlayerID) -> List[Game]:
#     def byPlayerIdAndAnalysed(self, playerId: PlayerID, analysed: bool = True) -> List[Game]:
#     def write(self, game: Game):
#     def writeMany(self, games: List[Game]):


from default_imports import *
from modules.game.EngineEval import EngineEval, EngineEvalBSONHandler
from modules.game.Game import Emt
from functools import lru_cache
from math import exp
import numpy as np
        adv = self.advantage()
        if usePV:
            r = self.trueRank()
            if r is not None:
                adv = winningChances(self.analyses[r-1].engineEval)
                
        return max(0, winningChances(self.top().engineEval) - adv)

    def advantage(self) -> Number:
        return winningChances(self.engineEval)

    def ambiguity(self) -> int: # 1 = only one top move, 5 = all moves good
        return sum(int(similarChances(winningChances(self.top().engineEval), winningChances(analysis.engineEval))) for analysis in self.analyses)

    def trueRank(self) -> TrueRank:
        return next((x+1 for x, am in enumerate(self.analyses) if am.uci == self.uci), None)

    def rank(self) -> Rank:
        return min(15, next((x for x, am in enumerate(self.analyses) if am.uci == self.uci), self.projectedRank()) + 1)

    def projectedRank(self) -> Number:
        if len(self.analyses) == 1:
            return 10
        else: # rise over run prediction of move rank given the difference between the winning chances in the bottom two analysed moves
            try:
                return len(self.analyses) + int(len(self.analyses)*abs(winningChances(self.analyses[-1].engineEval) - winningChances(self.engineEval)) / abs(winningChances(self.analyses[0].engineEval) - winningChances(self.analyses[-2].engineEval)))
            except ZeroDivisionError:
                return 10

@lru_cache(maxsize=64)

# Path: modules/game/EngineEval.py
# class EngineEval(NamedTuple('EngineEval', [
#         ('cp', Opt[Number]),
#         ('mate', Opt[int])
#     ])):
#     @staticmethod
#     def fromDict(d: Dict):
#         return EngineEval(d.get('cp', None), d.get('mate', None))
# 
#     def asdict(self) -> Dict:
#         return {'cp': self.cp} if self.cp is not None else {'mate': self.mate}
# 
#     def inverse(self):
#         return EngineEval(-self.cp if self.cp is not None else None,
#             -self.mate if self.mate is not None else None)
# 
#     def winningChances(self, colour: Colour) -> Number:
#         if self.mate is not None:
#             base = (1 if self.mate > 0 else 0)
#         else:
#             base = 1 / (1 + exp(-0.004 * self.cp))
#         return 100*(base if colour else (1-base))
# 
# class EngineEvalBSONHandler:
#     @staticmethod
#     def reads(bson: Dict) -> List[EngineEval]:
#         return EngineEval.fromDict(bson)
# 
#     def writes(engineEval: EngineEval) -> Dict:
#         return engineEval.asdict()
# 
# Path: modules/game/Game.py
# class Game(NamedTuple('Game', [
#         ('id', GameID),
#         ('white', PlayerID),
#         ('black', PlayerID),
#         ('pgn', List[str]),
#         ('emts', Opt[List[Emt]]),
#         ('analysis', Analysis)
#     ])):
# class GameBSONHandler:
# class GameDB(NamedTuple('GameDB', [
#         ('gameColl', Collection)
#     ])):
#     def fromDict(d: Dict):
#     def fromJson(json: Dict):
#     def toJson(self):
#     def playable(self):
#     def boardTensors(self, colour):
#     def boardTensorsByPlayerId(self, playerId: PlayerID, length: int = 60, safe: bool = True):
#     def tensor(self, playerId: PlayerID, length: int = 60, noisey: bool = False, safe: bool = True) -> Opt[GameTensor]:
#     def emtsByColour(self, colour: Colour, emts: Opt[List[int]] = None) -> List[Emt]:
#     def moveTensor(analysis: Analysis, emt: Emt, boardTensor: List[int], avgEmt: Number, colour: Colour) -> MoveTensor:
#     def nullBoardTensor():
#     def nullMoveTensor() -> MoveTensor:
#     def ply(moveNumber: int, colour: Colour) -> int:
#     def reads(bson: Dict) -> Game:
#     def writes(game: Game) -> Dict:
#     def byId(self, _id: GameID) -> Opt[Game]:
#     def byIds(self, ids: List[GameID]) -> List[Game]:
#     def byPlayerId(self, playerId: PlayerID) -> List[Game]:
#     def byPlayerIdAndAnalysed(self, playerId: PlayerID, analysed: bool = True) -> List[Game]:
#     def write(self, game: Game):
#     def writeMany(self, games: List[Game]):


# Path: modules/game/AnalysedMove.py
from default_imports import *
from modules.game.EngineEval import EngineEval, EngineEvalBSONHandler
from modules.game.Game import Emt
from functools import lru_cache
from math import exp
import numpy as np

        adv = self.advantage()
        if usePV:
            r = self.trueRank()
            if r is not None:
                adv = winningChances(self.analyses[r-1].engineEval)
                
        return max(0, winningChances(self.top().engineEval) - adv)

    def advantage(self) -> Number:
        return winningChances(self.engineEval)

    def ambiguity(self) -> int: # 1 = only one top move, 5 = all moves good
        return sum(int(similarChances(winningChances(self.top().engineEval), winningChances(analysis.engineEval))) for analysis in self.analyses)

    def trueRank(self) -> TrueRank:
        return next((x+1 for x, am in enumerate(self.analyses) if am.uci == self.uci), None)

    def rank(self) -> Rank:
        return min(15, next((x for x, am in enumerate(self.analyses) if am.uci == self.uci), self.projectedRank()) + 1)

    def projectedRank(self) -> Number:
        if len(self.analyses) == 1:
            return 10
        else: # rise over run prediction of move rank given the difference between the winning chances in the bottom two analysed moves
            try:
                return len(self.analyses) + int(len(self.analyses)*abs(winningChances(self.analyses[-1].engineEval) - winningChances(self.engineEval)) / abs(winningChances(self.analyses[0].engineEval) - winningChances(self.analyses[-2].engineEval)))
            except ZeroDivisionError:
                return 10

@lru_cache(maxsize=64)

def winningChances(engineEval: EngineEval) -> Number: