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

from . import atconnect

if TYPE_CHECKING:
	from atserver import AternosServer

OPT_PLAYERS = 'max-players'
OPT_GAMEMODE = 'gamemode'
OPT_DIFFICULTY = 'difficulty'
OPT_WHITELIST = 'white-list'
OPT_ONLINE = 'online-mode'
OPT_PVP = 'pvp'
OPT_CMDBLOCK = 'enable-command-block'
OPT_FLIGHT = 'allow-flight'
OPT_ANIMALS = 'spawn-animals'
OPT_MONSTERS = 'spawn-monsters'
OPT_VILLAGERS = 'spawn-npcs'
OPT_NETHER = 'allow-nether'
OPT_FORCEGM = 'force-gamemode'
OPT_SPAWNLOCK = 'spawn-protection'
OPT_CHEATS = 'allow-cheats'
OPT_RESOURCEPACK = 'resource-pack'

DAT_PREFIX = 'Data:'
DAT_SEED = 'RandomSeed'
DAT_HARDCORE = 'hardcore'
DAT_DIFFICULTY = 'Difficulty'

DAT_GR_PREFIX = 'Data:GameRules:'
DAT_GR_ADVS = 'announceAdvancements'
DAT_GR_CMDOUT = 'commandBlockOutput'
DAT_GR_ELYTRA = 'disableElytraMovementCheck'
DAT_GR_DAYLIGHT = 'doDaylightCycle'
DAT_GR_ENTDROPS = 'doEntityDrops'
DAT_GR_FIRETICK = 'doFireTick'
DAT_GR_LIMITCRAFT = 'doLimitedCrafting'
DAT_GR_MOBLOOT = 'doMobLoot'
DAT_GR_MOBS = 'doMobSpawning'
DAT_GR_TILEDROPS = 'doTileDrops'
DAT_GR_WEATHER = 'doWeatherCycle'
DAT_GR_KEEPINV = 'keepInventory'
DAT_GR_DEATHMSG = 'showDeathMessages'
DAT_GR_ADMINCMDLOG = 'logAdminCommands'
DAT_GR_CMDLEN = 'maxCommandChainLength'
DAT_GR_ENTCRAM = 'maxEntityCramming'
DAT_GR_MOBGRIEF = 'mobGriefing'
DAT_GR_REGEN = 'naturalRegeneration'
DAT_GR_RNDTICK = 'randomTickSpeed'
DAT_GR_SPAWNRADIUS = 'spawnRadius'
DAT_GR_REDUCEDF3 = 'reducedDebugInfo'
DAT_GR_SPECTCHUNK = 'spectatorsGenerateChunks'
DAT_GR_CMDFB = 'sendCommandFeedback'

DAT_TYPE_WORLD = 0
DAT_TYPE_GR = 1

GM_SURVIVAL = 0
GM_CREATIVE = 1
GM_ADVENTURE = 2
GM_SPECTATOR = 3

DF_PEACEFUL = 0
DF_EASY = 1
DF_NORMAL = 2
DF_HARD = 3

JAVA_JDK = 'openjdk:{}'
JAVA_OPENJ9 = 'adoptopenjdk:{}-jre-openj9-bionic'

FLAG_PROP_TYPE = 1

class AternosConfig:

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

		self.atserv = atserv

	@property
	def timezone(self) -> str:
		optreq = self.atserv.atserver_request(
			'https://aternos.org/options',
			atconnect.REQGET
		)
		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()

	@timezone.setter
	def timezone(self, value:str) -> None:
		matches_tz = re.search(r'(?:^[A-Z]\w+\/[A-Z]\w+$)|^UTC$', value)
		if matches_tz == None:
			raise AttributeError('Timezone must match zoneinfo format: Area/Location')

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

	@property
	def java_version(self) -> str:
		optreq = self.atserv.atserver_request(
			'https://aternos.org/options',
			atconnect.REQGET
		)
		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

	@java_version.setter
	def java_version(self, value:str) -> None:
		matches_jdkver = re.search(r'^(?:adopt)*openjdk:(\d+)(?:-jre-openj9-bionic)*$', value)
		if matches_jdkver == None:
			raise AttributeError('Java image version must match "[adopt]openjdk:%d[-jre-openj9-bionic]" format')

		self.atserv.atserver_request(
			'https://aternos.org/panel/ajax/image.php',
			atconnect.REQPOST, 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, flags:int=FLAG_PROP_TYPE) -> Dict[str,Any]:
		return self.__get_all_props('https://aternos.org/options', flags)

	def set_server_props(self, props:Dict[str,Any]) -> None:
		for key in props:
			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',
		flags:int=FLAG_PROP_TYPE) -> Dict[str,Any]:
		self.__get_all_props(
			f'https://aternos.org/files/{world}/level.dat',
			flags, [DAT_PREFIX, DAT_GR_PREFIX]
		)

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

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

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

	def __get_all_props(
		self, url:str, flags:int=FLAG_PROP_TYPE,
		prefixes:Optional[List[str]]=None) -> Dict[str,Any]:

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

		configs = opttree.xpath('//div[@class="config-options"]')
		for i in range(len(configs)):

			conf = configs[i]
			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 flags == FLAG_PROP_TYPE:

					if opttype == 'config-option-number'\
					or opttype == 'config-option-select':
						value = int(value)

					elif opttype == 'config-option-toggle':
						value = bool(value)
				result[key] = value
		return result
