# -*- coding: utf-8 -*-

"""This module provides access to user's settings on Diaspora*.
"""


import json
import os
import urllib
import warnings

BS4_SUPPORT=False
try:
	from bs4 import BeautifulSoup
except ImportError:
	import re
	print("[diaspy] BeautifulSoup not found, falling back on regex.")
else: BS4_SUPPORT=True

from diaspy import errors, streams


class Account():
	"""Provides interface to account settings.
	"""
	if not BS4_SUPPORT:
		email_regexp = re.compile('<input id="user_email" name="user\[email\]" size="30" type="text" value="(.+?)"')
		language_option_regexp = re.compile('<option value="([_a-zA-Z-]+)"(?: selected="selected")?>(.*?)</option>')

	def __init__(self, connection):
		self._connection = connection

	def downloadxml(self):
		"""Returns downloaded XML.
		"""
		request = self._connection.get('user/export')
		return request.text

	def downloadPhotos(self, size='large', path='.', mark_nsfw=True, _critical=False, _stream=None):
		"""Downloads photos into the current working directory.
		Sizes are: large, medium, small.
		Filename is: {post_guid}_{photo_guid}.{extension}

		Normally, this method will catch urllib-generated errors and
		just issue warnings about photos that couldn't be downloaded.
		However, with _critical param set to True errors will become
		critical - the will be reraised in finally block.

		:param size: size of the photos to download - large, medium or small
		:type size: str
		:param path: path to download (defaults to current working directory
		:type path: str
		:param mark_nsfw: will append '-nsfw' to images from posts marked as nsfw,
		:type mark_nsfw: bool
		:param _stream: diaspy.streams.Generic-like object (only for testing)
		:param _critical: if True urllib errors will be reraised after generating a warning (may be removed)

		:returns: integer, number of photos downloaded
		"""
		photos = 0
		if _stream is None:
			stream = streams.Activity(self._connection)
			stream.full()
		else:
			stream = _stream
		for i, post in enumerate(stream):
			if post['nsfw'] is not False: nsfw = '-nsfw'
			else: nsfw = ''
			if post['photos']:
				for n, photo in enumerate(post['photos']):
					# photo format -- .jpg, .png etc.
					ext = photo['sizes'][size].split('.')[-1]
					name = '{0}_{1}{2}.{3}'.format(post['guid'], photo['guid'], nsfw, ext)
					filename = os.path.join(path, name)
					try:
						urllib.request.urlretrieve(url=photo['sizes'][size], filename=filename)
					except (urllib.error.HTTPError, urllib.error.URLError) as e:
						warnings.warn('downloading image {0} from post {1}: {2}'.format(photo['guid'], post['guid'], e))
					finally:
						if _critical: raise
				photos += 1
		return photos

	def setEmail(self, email):
		"""Changes user's email.
		"""
		data = {'_method': 'put', 'utf8': '✓', 'user[email]': email, 'authenticity_token': repr(self._connection)}
		request = self._connection.post('user', data=data, allow_redirects=False)
		if request.status_code != 302:
			raise errors.SettingsError('setting email failed: {0}'.format(request.status_code))

	def getEmail(self):
		"""Returns currently used email.
		"""
		data = self._connection.get('user/edit')
		if BS4_SUPPORT:
			soup = BeautifulSoup(data.text, 'lxml')
			email = soup.find('input', {"id": "user_email"})
			if email: email = email['value']
			else: email = ''
			return email
		else:
			email = self.email_regexp.search(data.text)
			if email is None: email = ''
			else: email = email.group(1)
			return email

	def setLanguage(self, lang):
		"""Changes user's email.

		:param lang: language identifier from getLanguages()
		"""
		data = {'_method': 'put', 'utf8': '✓', 'user[language]': lang, 'authenticity_token': repr(self._connection)}
		request = self._connection.post('user', data=data, allow_redirects=False)
		if request.status_code != 302:
			raise errors.SettingsError('setting language failed: {0}'.format(request.status_code))

	def getLanguages(self):
		"""Returns a list of tuples containing ('Language name', 'identifier').
		One of the Black Magic(tm) methods.
		"""
		request = self._connection.get('user/edit')
		if BS4_SUPPORT:
			soup = BeautifulSoup(request.text, 'lxml')
			language = soup.find('select', {"id": "user_language"})
			return [(option['value'], option.text) for option in language.findAll('option')]
		else:
			return self.language_option_regexp.findall(request.text)


class Privacy():
	"""Provides interface to provacy settings.
	"""
	def __init__(self, connection):
		self._connection = connection


class Profile():
	"""Provides interface to profile settigns.

	WARNING:

		Because of the way update requests for profile are created every field must be sent.
		The `load()` method is used to load all information into the dictionary.
		Setters can then be used to adjust the data.
		Finally, `update()` can be called to send data back to pod.
	"""
	if not BS4_SUPPORT:
		firstname_regexp = re.compile('id="profile_first_name" name="profile\[first_name\]" type="text" value="(.*?)" />')
		lastname_regexp = re.compile('id="profile_last_name" name="profile\[last_name\]" type="text" value="(.*?)" />')
		bio_regexp = re.compile('<textarea id="profile_bio" name="profile\[bio\]" placeholder="Fill me out" rows="5">\n(.*?)</textarea>')
		location_regexp = re.compile('id="profile_location" name="profile\[location\]" placeholder="Fill me out" type="text" value="(.*?)" />')
		gender_regexp = re.compile('id="profile_gender" name="profile\[gender\]" placeholder="Fill me out" type="text" value="(.*?)" />')
		birth_year_regexp = re.compile('selected="selected" value="([0-9]{4,4})">[0-9]{4,4}</option>')
		birth_month_regexp = re.compile('selected="selected" value="([0-9]{1,2})">(.*?)</option>')
		birth_day_regexp = re.compile('selected="selected" value="([0-9]{1,2})">[0-9]{1,2}</option>')
		is_searchable_regexp = re.compile('checked="checked" id="profile_searchable" name="profile\[searchable\]" type="checkbox" value="(.*?)" />')
		is_nsfw_regexp = re.compile('checked="checked" id="profile_nsfw" name="profile\[nsfw\]" type="checkbox" value="(.*?)" />')

	def __init__(self, connection, no_load=False):
		self._connection = connection
		self.data = {'utf-8': '✓',
					'_method': 'put',
					'profile[first_name]': '',
					'profile[last_name]': '',
					'profile[tag_string]': '',
					'tags': '',
					'file': '',
					'profile[bio]': '',
					'profile[location]': '',
					'profile[gender]': '',
					'profile[date][year]': '',
					'profile[date][month]': '',
					'profile[date][day]': '',
					}
		self._html = self._fetchhtml()
		self._loaded = False
		if not no_load: self.load()

	def _fetchhtml(self):
		"""Fetches html that will be used to extract data.
		"""
		return self._connection.get('profile/edit').text

	def getName(self):
		"""Returns two-tuple: (first, last) name.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			first = soup.find('input', {"id": "profile_first_name"})
			last = soup.find('input', {"id": "profile_last_name"})
			return (first['value'], last['value'])
		else:
			first = self.firstname_regexp.search(self._html).group(1)
			last = self.lastname_regexp.search(self._html).group(1)
			return (first, last)

	def getTags(self):
		"""Returns tags user had selected when describing him/her-self.
		"""
		guid = self._connection.getUserData()['guid']
		html = self._connection.get('people/{0}'.format(guid)).text
		if BS4_SUPPORT:
			soup = BeautifulSoup(html, 'lxml')
			tags = soup.find('meta', {"name": "keywords"})
			return [tag.lower() for tag in tags['content'].split(", ")]
		else:
			description_regexp = re.compile('<a href="/tags/(.*?)" class="tag">#.*?</a>')
			return [tag.lower() for tag in re.findall(description_regexp, html)]

	def getBio(self):
		"""Returns user bio.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			bio = soup.find('textarea', {"id": "profile_bio"})
			return bio.get_text()
		else:
			bio = self.bio_regexp.search(self._html).group(1)
			return bio

	def getLocation(self):
		"""Returns location string.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			location = soup.find('input', {"id": "profile_location"})
			return location['value']
		else:
			location = self.location_regexp.search(self._html).group(1)
			return location

	def getGender(self):
		"""Returns location string.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			gender = soup.find('input', {"id": "profile_gender"})
			return gender['value']
		else:
			gender = self.gender_regexp.search(self._html).group(1)
			return gender

	def getBirthDate(self, named_month=False):
		"""Returns three-tuple: (year, month, day).

		:param named_month: if True, return name of the month instead of integer
		:type named_month: bool
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')

			year = soup.find('select', {"id": "profile_date_year"})
			year_option = year.find('option', selected=True)
			if year_option is None: year_option = -1
			else: year_option = int(year_option['value'])

			month = soup.find('select', {"id": "profile_date_month"})
			month_option = month.find('option', selected=True)
			if month_option is None:
				if named_month: month_option = ''
				else: month_option = -1
			elif named_month:
				month_option = month_option.text
			else: month_option = int(month_option['value'])

			day = soup.find('select', {"id": "profile_date_day"})
			day_option = day.find('option', selected=True)
			if day_option is None: day_option = -1
			else: day_option = int(day_option['value'])
			return (year_option, month_option, day_option)
		else:
			year = self.birth_year_regexp.search(self._html)
			if year is None: year = -1
			else: year = int(year.group(1))
			month = self.birth_month_regexp.search(self._html)
			if month is None:
				if named_month: month = ''
				else: month = -1
			else:
				if named_month:
					month = month.group(2)
				else:
					month = int(month.group(1))
			day = self.birth_day_regexp.search(self._html)
			if day is None: day = -1
			else: day = int(day.group(1))
			return (year, month, day)

	def isSearchable(self):
		"""Returns True if profile is searchable.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			searchable = soup.find('input', {"id": "profile_searchable"})
			if (searchable.has_attr('checked') and
					searchable['checked'] == 'checked'):
				return True
			else: return False
		else:
			searchable = self.is_searchable_regexp.search(self._html)
			# this is because value="true" in every case so we just
			# check if the field is "checked"
			if searchable is None: searchable = False  # if it isn't - the regexp just won't match
			else: searchable = True
			return searchable

	def isNSFW(self):
		"""Returns True if profile is marked as NSFW.
		"""
		if BS4_SUPPORT:
			soup = BeautifulSoup(self._html, 'lxml')
			nsfw = soup.find('input', {"id": "profile_nsfw"})
			if (nsfw.has_attr('checked') and
					nsfw['checked'] == 'checked'):
				return True
			else: return False
		else:
			nsfw = self.is_nsfw_regexp.search(self._html)
			if nsfw is None: nsfw = False
			else: nsfw = True
			return nsfw

	def setName(self, first, last):
		"""Set first and last name.
		"""
		self.data['profile[first_name]'] = first
		self.data['profile[last_name]'] = last

	def setTags(self, tags):
		"""Sets tags that describe the user.
		"""
		self.data['tags'] = ', '.join(['#{}'.format(tag) for tag in tags])

	def setBio(self, bio):
		"""Set bio of a user.
		"""
		self.data['profile[bio]'] = bio

	def setLocation(self, location):
		"""Set location of a user.
		"""
		self.data['profile[location]'] = location

	def setGender(self, gender):
		"""Set gender of a user.
		"""
		self.data['profile[gender]'] = gender

	def setBirthDate(self, year, month, day):
		"""Set birth date of a user.
		"""
		self.data['profile[date][year]'] = year
		self.data['profile[date][month]'] = month
		self.data['profile[date][day]'] = day

	def setSearchable(self, searchable):
		"""Set user's searchable status.
		"""
		self.data['profile[searchable]'] = json.dumps(searchable)

	def setNSFW(self, nsfw):
		"""Set user NSFW status.
		"""
		self.data['profile[nsfw]'] = json.dumps(nsfw)

	def load(self):
		"""Loads profile data into self.data dictionary.
		**Notice:** Not all keys are loaded yet.
		"""
		self.setName(*self.getName())
		self.setBio(self.getBio())
		self.setLocation(self.getLocation())
		self.setGender(self.getGender())
		self.setBirthDate(*self.getBirthDate(named_month=False))
		self.setSearchable(self.isSearchable())
		self.setNSFW(self.isNSFW())
		self.setTags(self.getTags())
		self._loaded = True

	def update(self):
		"""Updates profile information.
		"""
		if not self._loaded: raise errors.DiaspyError('profile was not loaded')
		self.data['authenticity_token'] = repr(self._connection)
		request = self._connection.post('profile', data=self.data, allow_redirects=False)
		return request.status_code


class Services():
	"""Provides interface to services settings.
	"""
	def __init__(self, connection):
		self._connection = connection
