import enum
import re
import lxml.html
from typing import Any, Dict, List, Optional
from typing import TYPE_CHECKING

if TYPE_CHECKING:
	from .atserver import AternosServer

#
# server.options
class ServerOpts(enum.Enum):
	players = 'max-players'
	gm = 'gamemode'
	difficulty = 'difficulty'
	whl = 'white-list'
	online = 'online-mode'
	pvp = 'pvp'
	cmdblock = 'enable-command-block'
	flight = 'allow-flight'
	animals = 'spawn-animals'
	monsters = 'spawn-monsters'
	villagers = 'spawn-npcs'
	nether = 'allow-nether'
	forcegm = 'force-gamemode'
	spawnlock = 'spawn-protection'
	cmds = 'allow-cheats'
	packreq = 'require-resource-pack'
	pack = 'resource-pack'

DAT_PREFIX = 'Data:'
DAT_GR_PREFIX = 'Data:GameRules:'

# level.dat
class WorldOpts(enum.Enum):
	seed12 = 'randomseed'
	seed = 'seed'
	hardcore = 'hardcore'
	difficulty = 'Difficulty'

# /gamerule
class WorldRules(enum.Enum):
	advs = 'announceAdvancements'
	univanger = 'universalAnger'
	cmdout = 'commandBlockOutput'
	elytra = 'disableElytraMovementCheck'
	raids = 'disableRaids'
	daynight = 'doDaylightCycle'
	entdrop = 'doEntityDrops'
	fire = 'doFireTick'
	phantoms = 'doInsomnia'
	immrespawn = 'doImmediateRespawn'
	limitcraft = 'doLimitedCrafting'
	mobloot = 'doMobLoot'
	mobs = 'doMobSpawning'
	patrols = 'doPatrolSpawning'
	blockdrop = 'doTileDrops'
	traders = 'doTraderSpawning'
	weather = 'doWeatherCycle'
	drowndmg = 'drowningDamage'
	falldmg = 'fallDamage'
	firedmg = 'fireDamage'
	snowdmg = 'freezeDamage'
	forgive = 'forgiveDeadPlayers'
	keepinv = 'keepInventory'
	deathmsg = 'showDeathMessages'
	admincmdlog = 'logAdminCommands'
	cmdlen = 'maxCommandChainLength'
	entcram = 'maxEntityCramming'
	mobgrief = 'mobGriefing'
	regen = 'naturalRegeneration'
	sleeppct = 'playersSleepingPercentage'
	rndtick = 'randomTickSpeed'
	spawnradius = 'spawnRadius'
	reducedf3 = 'reducedDebugInfo'
	spectchunkgen = 'spectatorsGenerateChunks'
	cmdfb = 'sendCommandFeedback'

DAT_TYPE_WORLD = 0
DAT_TYPE_GR = 1

class Gamemode(enum.IntEnum):
	survival = 0
	creative = 1
	adventure = 2
	spectator = 3

class Difficulty(enum.IntEnum):
	peaceful = 0
	easy = 1
	normal = 2
	hard = 3

#
# jre types for set_java
javatype = {
	'jdk': 'openjdk:{ver}',
	'openj9-1': 'adoptopenjdk:{ver}-jre-openj9-bionic',
	'openj9-2': 'ibm-semeru-runtimes:open-{ver}-jre'
}
# checking java version format
javacheck = re.compile(
	''.join(
		list(
			map(
				# create a regexp for each jre type,
				# e.g.: (^openjdk:\d+$)|
				lambda i: '(^' + javatype[i].format(ver=r'\d+') + '$)|',
				javatype
			)
		)
	).rstrip('|')
)
# checking timezone format
tzcheck = re.compile(r'(^[A-Z]\w+\/[A-Z]\w+$)|^UTC$')
# options types converting
convert = {
	'config-option-number': int,
	'config-option-select': int,
	'config-option-toggle': bool
}

# MAIN CLASS
class AternosConfig:

	def __init__(self, atserv:'AternosServer') -> None:

		self.atserv = atserv

	def get_timezone(self) -> str:

		optreq = self.atserv.atserver_request(
			'https://aternos.org/options', 'GET'
		)
		opttree = lxml.html.fromstring(optreq)

		tzopt = opttree.xpath('//div[@class="options-other-input timezone-switch"]')[0]
		tztext = tzopt.xpath('.//div[@class="option current"]')[0].text
		return tztext.strip()

	def set_timezone(self, value:str) -> None:

		matches_tz = tzcheck.search(value)
		if not matches_tz:
			raise ValueError('Timezone must match zoneinfo format: Area/Location')

		self.atserv.atserver_request(
			'https://aternos.org/panel/ajax/timezone.php',
			'POST', data={'timezone': value},
			sendtoken=True
		)

	def get_java(self) -> str:

		optreq = self.atserv.atserver_request(
			'https://aternos.org/options', 'GET'
		)
		opttree = lxml.html.fromstring(optreq)
		imgopt = opttree.xpath('//div[@class="options-other-input image-switch"]')[0]
		imgver = imgopt.xpath('.//div[@class="option current"]/@data-value')[0]
		return imgver

	def set_java(self, value:str) -> None:

		matches_jdkver = javacheck.search(value)
		if not matches_jdkver:
			raise ValueError('Incorrect Java image version format!')

		self.atserv.atserver_request(
			'https://aternos.org/panel/ajax/image.php',
			'POST', data={'image': value},
			sendtoken=True
		)

	#
	# server.properties
	#
	def set_server_prop(self, option:str, value:Any) -> None:
		self.__set_prop(
			'/server.properties',
			option, value
		)

	def get_server_props(self, proptyping:bool=True) -> Dict[str,Any]:
		return self.__get_all_props('https://aternos.org/options', proptyping)

	def set_server_props(self, props:Dict[str,Any]) -> None:
		for key in props:
			self.set_server_prop(key, props[key])

	#
	# level.dat
	#
	def set_world_prop(
		self, option:str, value:Any,
		proptype:int, world:str='world') -> None:

		prefix = DAT_PREFIX
		if proptype == DAT_TYPE_GR:
			prefix = DAT_GR_PREFIX

		self.__set_prop(
			f'/{world}/level.dat',
			f'{prefix}{option}',
			value
		)

	def get_world_props(
		self, world:str='world',
		proptyping:bool=True) -> Dict[str,Any]:

		self.__get_all_props(
			f'https://aternos.org/files/{world}/level.dat',
			proptyping, [DAT_PREFIX, DAT_GR_PREFIX]
		)

	def set_world_props(self, props:Dict[str,Any]) -> None:
		for key in props:
			self.set_world_prop(key, props[key])

	#
	# helpers
	#
	def __set_prop(self, file:str, option:str, value:Any) -> None:

		self.atserv.atserver_request(
			'https://aternos.org/panel/ajax/config.php',
			'POST', data={
				'file': file,
				'option': option,
				'value': value
			}, sendtoken=True
		)

	def __get_all_props(
		self, url:str, proptyping:bool=True,
		prefixes:Optional[List[str]]=None) -> Dict[str,Any]:

		optreq = self.atserv.atserver_request(url, 'GET')
		opttree = lxml.html.fromstring(optreq.content)
		configs = opttree.xpath('//div[@class="config-options"]')

		for i, conf in enumerate(configs):
			opts = conf.xpath('/div[contains(@class,"config-option ")]')
			result = {}

			for opt in opts:
				key = opt.xpath('.//span[@class="config-option-output-key"]')[0].text
				value = opt.xpath('.//span[@class="config-option-output-value"]')[0].text

				if prefixes != None:
					key = f'{prefixes[i]}{key}'

				opttype = opt.xpath('/@class').split(' ')[1]
				if proptyping and opttype in convert:
					value = convert[opttype](value)

				result[key] = value

		return result
