import re
import warnings
from optparse import make_option
from django.core.management.commands.inspectdb import Command as InspectDBCommand
from django.db import connections, DEFAULT_DB_ALIAS
from django.utils import six
from salesforce.backend import introspection as sf_introspection
from salesforce import DJANGO_15_PLUS
import django
import salesforce

try:
	from collections import OrderedDict
except ImportError:
	# Python 2.6-
	from django.utils.datastructures import SortedDict as OrderedDict


def fix_field_params_repr(params):
	"""
	Fixes repr() of "field_params" for Python 2 with future unicode_literals.
	"""
	class ReprUnicode(six.text_type):
		def __new__(cls, text):
			return unicode.__new__(cls, text)
		def __repr__(self):
			out = repr(unicode(self))
			return out[1:] if out.startswith("u'") or out.startswith('u"') else out
	class ReprChoices(list):
		def __new__(cls, choices):
			return list.__new__(cls, choices)
		def __repr__(self):
			out = []
			for x0, x1 in self:
				out.append('(%s, %s)' % (
						repr(ReprUnicode(x0) if isinstance(x0, unicode) else x0),
						repr(ReprUnicode(x1) if isinstance(x1, unicode) else x1)
				))
			return '[%s]' % (', '.join(out))
	if not DJANGO_15_PLUS or six.PY3:
		return params
	out = OrderedDict()
	for k, v in params.items():
		if k == 'choices' and v:
			v = ReprChoices(v)
		elif isinstance(v, unicode):
			v = ReprUnicode(v)
		out[k] = v
	return out

def fix_international(text):
	"Fix excaped international characters back to utf-8"
	class SmartInternational(str):
		def __new__(cls, text):
			return str.__new__(cls, text)
		def endswith(self, string):
			return super(SmartInternational, self).endswith(str(string))
	if six.PY3:
		return text
	out = []
	last = 0
	for match in re.finditer(r'(?<=[^\\])(?:\\x[0-9a-f]{2}|\\u[0-9a-f]{4})', text):
		start, end, group = match.start(), match.end(), match.group()
		out.append(text[last:start])
		c = group.decode('unicode_escape')
		out.append(c if ord(c) >160 and ord(c) != 173 else group)
		last = end
	out.append(text[last:])
	return SmartInternational(''.join(out).encode('utf-8'))


class Command(InspectDBCommand):
	# This will export Salestorce to a valid models.py, if Django >=1.5.
	# It is recommended to use Django >=1.5 for inspectdb, even if the generated models.py will be used on Django <1.5
	# (The model generated by Django <=1.4 requires very much manual editing, adding many `related_name=...`)

	option_list = InspectDBCommand.option_list + (
		make_option('--table-filter', action='store', dest='table_name_filter',
			help='Regular expression that filters API Names of SF tables to introspect.'),
	)

	def handle_noargs(self, **options):
		if isinstance(options['table_name_filter'], str):
			options['table_name_filter'] = re.compile(options['table_name_filter']).match
		self.verbosity = int(options['verbosity'])
		self.connection = connections[options['database']]
		if self.connection.vendor == 'salesforce':
			if not six.PY3:
				self.stdout.write("# coding: utf-8\n")
			self.db_module = 'salesforce'
			for line in self.handle_inspection(options):
				line = line.replace(" Field renamed because it contained more than one '_' in a row.", "")
				line = re.sub(' #$', '', line)
				if django.VERSION[:2] < (1,5):
					# prevent problems with mutual dependencies etc.
					line = re.sub(r'(?<=models.ForeignKey\()(\w+)',  r"'\1'", line)
				elif django.VERSION[:2] == (1,5):
					# fix bug in Django 1.5
					line = line.replace("''self''", "'self'")
				self.stdout.write(fix_international("%s\n" % line))
		else:
			super(Command, self).handle_noargs(**options)


	def get_field_type(self, connection, table_name, row):
		field_type, field_params, field_notes = super(Command, self
				).get_field_type(connection, table_name, row)
		if connection.vendor == 'salesforce':
			name, type_code, display_size, internal_size, precision, scale, null_ok, sf_params = row
			if 'ref_comment' in sf_params:
				field_notes.append(sf_params.pop('ref_comment'))
			field_params.update(sf_params)
		return field_type, fix_field_params_repr(field_params), field_notes

	def normalize_col_name(self, col_name, used_column_names, is_relation):
		if self.connection.vendor == 'salesforce':
			beautified = re.sub('__c$', '', col_name)
			beautified = re.sub(r'([a-z0-9])(?=[A-Z])', r'\1_', beautified)
			beautified = beautified.lower()
			new_name, field_params, field_notes = super(Command, self
					).normalize_col_name(beautified, used_column_names, is_relation)
			# *reconstructed* : is what will SfField reconstruct to db column
			field_params = OrderedDict(sorted(field_params.items()))
			reconstructed = new_name.title().replace('_', '')
			if col_name.endswith('__c'):
				reconstructed += '__c'
				field_params['custom'] = True
			elif is_relation:
				reconstructed += 'Id'
			# TODO: Discuss: Maybe 'db_column' can be compared case insensitive,
			#       but exact compare is safer.
			if reconstructed != col_name or self.verbosity >= 2:
				field_params['db_column'] = col_name
			else:
				field_params.pop('db_column', None)
			if is_relation:
				if col_name in sf_introspection.last_with_important_related_name:
					field_params['related_name'] = '%s_%s_set' % (
							sf_introspection.last_introspected_model.lower(),
							new_name.replace('_', '')
							)
				if col_name in sf_introspection.last_read_only:
					field_params['sf_read_only'] = sf_introspection.last_read_only[col_name]
				field_params['on_delete'] = sf_introspection.SymbolicModelsName('DO_NOTHING')
				if len(sf_introspection.last_refs[col_name]) > 1:
					field_notes.append('Reference to tables [%s]' % (', '.join(sf_introspection.last_refs[col_name])))
		else:
			new_name, field_params, field_notes = super(Command, self
					).normalize_col_name(col_name, used_column_names, is_relation)
		return new_name, fix_field_params_repr(field_params), field_notes

	def get_meta(self, table_name):
		"""
		Return a sequence comprising the lines of code necessary
		to construct the inner Meta class for the model corresponding
		to the given database table name.
		"""
		ret =  ["    class Meta(models.Model.Meta):",
			"        db_table = '%s'" % table_name,
			]
		if self.connection.vendor == 'salesforce':
			for line in self.connection.introspection.get_additional_meta(table_name):
				ret.append("        " + line)
		ret.append("")
		return ret


if django.VERSION[:2] < (1,5):
	warnings.warn("Django >= 1.5 is required to generate a valid model. "
			"Manual editing is necessary for older Django.")
