# encoding=utf-8
"""
file for the CotationWindow top object
"""

__author__ = "Roland Trouville"
__copyright__ = "Copyright 2015+, Consortium MonPaGe"
__license__ = "Creative Commons 4.0 By-Nc-Sa"
__maintainer__ = "Roland Trouville"
__email__ = "contact.monpage@gmail.com"
__status__ = "Production"

import logging

from datetime import datetime

from reportlab.platypus.flowables import Flowable
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate
from reportlab.platypus import Table, TableStyle, Paragraph, Image, PageBreak

from reporting.indicator_reporting import IndicatorReporting
from tools.general_tools import GeneralTools
from tools.participant_manager import ParticipantManager
from tools.ui_tools import UITools

logger = logging.getLogger(__name__)


class VerticalText(Flowable):
	def __init__(self, text):
		Flowable.__init__(self)
		self.text = text

	def draw(self):
		"""
		Draw the text rotated 90 degrees on the canvas.

		Rotates the canvas by 90 degrees, translates the origin to adjust
		for font size, then draws the string at the new origin.

		:return: None
		"""
		canvas = self.canv
		canvas.rotate(90)
		fs = canvas._fontsize
		canvas.translate(1, -fs / 1.2)  # canvas._leading?
		canvas.drawString(0, 0, self.text)

	def wrap(self, aW, aH):
		"""
		Calculate the wrap size of the text.

		Returns the vertical space required (leading) and horizontal space
		needed (string width plus 1 unit).

		:param aW: Available width (not used in calculation).
		:param aH: Available height (not used in calculation).
		:return: Tuple (height, width) needed to render the text.
		"""
		canv = self.canv
		fn, fs = canv._fontname, canv._fontsize
		return canv._leading, 1 + canv.stringWidth(self.text, fn, fs)


class PDFReporting(object):
	"""
	Class to generate a PDF report containing participant indicators with detailed tables
	and graphical elements.

	Attributes:
	    group_names (dict): Mapping of group IDs to their descriptive names.
	    default_colors (list): List of colors used for table styling.
	    participant_code (str): Code identifying the participant.
	    judge_code (str): Code identifying the judge.
	    session_date (str): Date of the session.
	    output_file (str): File path where the PDF will be saved.
	    error_messages (dict): Mapping of error messages to numeric references.
	    error_counter (int): Counter for assigning numeric references to errors.
	    elements (list): List of ReportLab flowables to build the PDF content.
	    doc (SimpleDocTemplate): ReportLab PDF document object.
	"""

	participant_code: str = None
	judge_code: str = None
	session_date: str = None

	output_file = None

	doc = None
	elements = []

	footer_height = 10 * mm
	header_height = 5 * mm

	error_messages: dict = {}
	error_counter: int = 1

	group_names: dict = {
		1: "Voix",
		2: "Voyelles",
		3: "",
		4: "Débit",
		5: "",
		6: "Diadococinésies (sur 4 secondes)",
		7: "Diadococinésies (sur 2 secondes)",
		8: "Diadococinésies (sur 4 secondes)",
		9: "Diadococinésies (sur 2 secondes)",
		10: "",
		11: "",
	}

	default_indicator_colors = [
		colors.Color(0.60, 0.60, 1),
		colors.Color(0.65, 0.65, 1),
		colors.Color(0.70, 0.70, 1),
		colors.Color(0.74, 0.74, 1),
	]

	def __init__(self, participant_code, session_date, judge_code, ouput_file=None):
		super(PDFReporting, self).__init__()
		self.participant_code = participant_code
		self.judge_code = judge_code
		self.session_date = session_date
		self.error_messages = {}
		self.error_counter = 1
		self.init()
		self.output_file = ouput_file
		if ouput_file is None:
			self.output_file = self.get_output_filename()

	def init(self):
		"""
		Initialize participant and reporting context.

		Sets the current participant code in ParticipantManager and updates
		IndicatorReporting with current participant, judge, and session date.
		"""
		ParticipantManager.set_current_participant(self.participant_code)
		IndicatorReporting.set_current_values(
			self.participant_code, self.judge_code, self.session_date
		)
		PDFReporting.calculate_and_save_perceptive_scores_for_reporting()

	@staticmethod
	def calculate_and_save_perceptive_scores_for_reporting():
		if ParticipantManager.current_participant is not None:
			wnr = IndicatorReporting.get_words_not_recognized()
			segerr = IndicatorReporting.get_segmental_errors()

			if wnr is not None:
				IndicatorReporting.save_acoustic_value(52, wnr, None)
			if segerr is not None:
				IndicatorReporting.save_acoustic_value(53, segerr, None)

	@staticmethod
	def get_output_filename() -> str:
		"""
		Retrieve the filename for the reporting output.

		:return: The path or name of the output report file.
		"""
		return IndicatorReporting.get_reporting_filename()

	def get_session_date_as_num(self) -> tuple:
		"""
		Convert session date string to a tuple of integers (year, month, day).

		Assumes session_date is formatted as 'YYYY_MM_DD'.

		:return: Tuple (year, month, day) as integers.
		"""
		tmp = self.session_date.split("_")
		return int(tmp[0]), int(tmp[1]), int(tmp[2])

	def create_file(self):
		"""
		Initialize the PDF document and prepare the elements list.

		Sets up a SimpleDocTemplate for the output file with A4 page size,
		and initializes an empty list to hold document elements.
		"""
		self.doc = SimpleDocTemplate(self.output_file, pagesize=A4)
		self.elements = []

	def generate_indicator_table(self):
		"""
		Generate and append an indicator table to the PDF report.

		This method creates a styled table summarizing participant indicators
		with associated values and graphical icons. It fetches active and displayed
		indicators from the database, processes them by group, applies conditional
		color styling and cell spanning, and appends the table and error messages
		to the PDF elements list.

		Key features:
		- Defines table styles including borders, fonts, alignment, and background colors.
		- Loads icons for warnings and directional arrows used in headers.
		- Retrieves indicators with their metadata from the database.
		- Processes indicator groups to resolve conflicting groups (prefers group 6+8 or 7+9).
		- Adds data rows with appropriate cell spans and background colors based on indicator properties.
		- Creates a ReportLab Table object with specified column widths and row heights.
		- Appends the table and formatted error messages to the PDF elements for rendering.

		Raises:
			Logs errors if issues occur during table generation but does not interrupt flow.
		"""
		LIST_STYLE = TableStyle(
			[
				("BOX", (0, 0), (-1, -1), 1, colors.black),
				("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black),
				("LINEBEFORE", (2, 0), (2, -1), 1, colors.black),
				("LINEBEFORE", (-10, 0), (-10, -1), 1, colors.black),
				("LINEBEFORE", (-9, 0), (-9, -1), 1, colors.black),
				("LINEBEFORE", (-8, 0), (-8, -1), 1, colors.black),
				("FONTSIZE", (1, 0), (-1, -1), 8),
				("FONTSIZE", (-8, 0), (-1, 0), 7),
				("FONTSIZE", (0, 0), (0, -1), 5),
				("ALIGN", (5, 0), (-1, -1), "CENTER"),
				("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
			]
		)
		LIST_STYLE.add(
			"BACKGROUND", (-8, 0), (-1, -1), PDFReporting.default_indicator_colors[0]
		)
		LIST_STYLE.add(
			"BACKGROUND", (-7, 0), (-2, -1), PDFReporting.default_indicator_colors[1]
		)
		LIST_STYLE.add(
			"BACKGROUND", (-6, 0), (-3, -1), PDFReporting.default_indicator_colors[2]
		)
		LIST_STYLE.add(
			"BACKGROUND", (-5, 0), (-4, -1), PDFReporting.default_indicator_colors[3]
		)

		LIST_STYLE.add("SPAN", (2, 0), (3, 0))

		warning = Image("./ui/icons/warning.png", 5 * mm, 5 * mm)
		patho = Image("./ui/icons/sens_patho.png", 5 * mm, 5 * mm)
		arrow_both = Image("./ui/icons/double_arrow.png", 3 * mm, 3 * mm)
		arrow_low = Image("./ui/icons/left_arrow.png", 3 * mm, 3 * mm)
		arrow_high = Image("./ui/icons/right_arrow.png", 3 * mm, 3 * mm)

		headers = (
			"",
			"Indicateur",
			"Valeur",
			"",
			warning,
			patho,
			"<1[",
			"[1, 5[",
			"[5, 10[",
			"[10, 50[",
			"[50, 90[",
			"[90, 95[",
			"[95, 99[",
			"[99+",
		)

		wanted_indicators = {}
		sql = (
			"Select `code`, `group`, `2nd_score_code`, `direction` from indicator "
			"where display=1 and active = 1 order by `group` ASC, `order` ASC "
		)
		tmp = IndicatorReporting.participant_result_db.execute(sql).fetchall()
		for line in tmp:
			code = line["code"]
			sub_score_code = line["2nd_score_code"]
			dir = line["direction"]
			direction = arrow_both
			if dir == "l":
				direction = arrow_low
			elif dir == "h":
				direction = arrow_high
			group = int(line["group"])
			if group not in wanted_indicators:
				wanted_indicators[group] = []
			data = self.__generate_one_indicator_line__(
				code, group, sub_score_code, direction
			)
			wanted_indicators[group].append(data)

		# We have to do some post treatment on the data (mainly on error codes and groups)
		# We either want 4s (6+8) or 2s (7+9)
		nb_4s = 0
		nb_2s = 0
		for gid in (6, 7, 8, 9):
			if gid in wanted_indicators:
				for w in wanted_indicators[gid]:
					if w[0]:
						if gid in (6, 8):
							nb_4s += 1
						else:
							nb_2s += 1

		if nb_4s > nb_2s:
			wanted_indicators.pop(7, None)
			wanted_indicators.pop(9, None)
		else:
			wanted_indicators.pop(6, None)
			wanted_indicators.pop(8, None)

		data = [headers]
		line_id = 1
		try:
			for group_id in wanted_indicators:
				LIST_STYLE.add(
					"SPAN",
					(0, line_id),
					(0, line_id + len(wanted_indicators[group_id]) - 1),
				)
				LIST_STYLE.add(
					"BOX",
					(0, line_id),
					(-1, line_id + len(wanted_indicators[group_id]) - 1),
					1,
					colors.black,
				)
				for w in wanted_indicators[group_id]:
					data.append(w[2])
					if not w[0]:
						LIST_STYLE.add(
							"BACKGROUND",
							(2, line_id),
							(-1, line_id),
							colors.Color(1, 1, 1),
						)
					if not w[3]:  # We have empty val2, we span
						LIST_STYLE.add("SPAN", (2, line_id), (3, line_id))
					line_id += 1
		except Exception as e:
			logger.error(f"Error generating indicator table: {e}")
		table = Table(
			data,
			colWidths=[
				5 * mm,
				55 * mm,
				20 * mm,
				20 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
				9 * mm,
			],
			rowHeights=5 * mm,
			spaceBefore=0 * mm,
		)
		table.setStyle(LIST_STYLE)
		self.elements.append(table)
		chaine = ""
		for er in self.error_messages:
			chaine += (self.error_messages[er] * "*") + ": " + er + "<br />"
		self.elements.append(Paragraph(chaine, getSampleStyleSheet()["Normal"]))

	def __generate_one_indicator_line__(
		self,
		indicator_code: str = None,
		group_id: int = None,
		sub_score_code: str = None,
		direction: Image = None,
	) -> tuple:
		res = IndicatorReporting.get_indicator_percentile_value(indicator_code)
		val2 = ""
		if sub_score_code is not None and sub_score_code != "":
			res2 = IndicatorReporting.get_indicator_percentile_value(sub_score_code)
			if res2 is not None and res2["value"] is not None:
				val2 = "{:.2f}".format(float(res2["value"]))
				val2 += " " + res2["unit"]
		col_id = {
			"0-1": 0,
			"1-5": 1,
			"5-10": 2,
			"10-50": 3,
			"50-90": 4,
			"90-95": 5,
			"95-99": 6,
			"99-100": 7,
		}
		pos_distrib = ["", "", "", "", "", "", "", ""]
		have_result = False
		val = None
		dir = None

		if res["percentile_above"] is not None and res["percentile_below"] is not None:
			have_result = True
			percentile_bracket = (
				str(res["percentile_above"]) + "-" + str(res["percentile_below"])
			)
			pos_distrib[col_id[percentile_bracket]] = "x"
			val = "{:.2f}".format(float(res["value"]))
			val += " " + res["unit"]
			dir = direction

		erreur = res["erreur"]
		if erreur is not None and erreur != "":
			if erreur not in self.error_messages:
				self.error_messages[erreur] = self.error_counter
				self.error_counter += 1
			erreur = "*" * self.error_messages[erreur]

		t = (
			VerticalText(self.group_names[group_id]),
			res["label"],
			val,
			val2,
			erreur,
			dir,
			pos_distrib[0],
			pos_distrib[1],
			pos_distrib[2],
			pos_distrib[3],
			pos_distrib[4],
			pos_distrib[5],
			pos_distrib[6],
			pos_distrib[7],
		)
		return have_result, indicator_code, t, val2 != ""

	def generate_devscore_table(self):
		"""
		Generate and append the development score table to the PDF report.

		This method builds a detailed table summarizing various developmental
		scores and measures for the participant. It includes color-coded scores,
		grouped dimensions, and total/composite scores with special formatting.

		Key features:
		- Defines background colors for different score ranges.
		- Specifies table style including borders, fonts, alignment, and spans.
		- Constructs table headers and rows based on a predefined list of metrics.
		- Colors the score cells according to their respective deviance score.
		- Adds a total deviance score row with conditional formatting based on score thresholds.
		- Applies cell spanning and boxed outlines to group related rows visually.
		- Appends the completed table to the PDF elements list.
		- Adds a warning paragraph if some scores might be underestimated due to missing values.

		Notes:
		- Uses `__generate_one_devscore_line__` internally to get row data and score.
		- Relies on external data sources like `IndicatorReporting` and `GeneralTools`.

		"""
		colors_by_score = {
			0: colors.Color(0.004, 0.69, 0.31),
			1: colors.Color(1, 0.85, 0.36),
			2: colors.Color(0.92, 0.57, 0.25),
			3: colors.Color(0.84, 0.29, 0.13),
			4: colors.Color(0.75, 0, 0),
			-1: colors.Color(0.75, 0.75, 0.75),
		}
		LIST_STYLE = TableStyle(
			[
				("BOX", (0, 0), (-1, -1), 1, colors.black),
				("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black),
				# ('LINEBEFORE', (4, 0), (4, -1), 1, colors.black),
				# ('FONTSIZE', (1, 0), (-1, -1), 8),
				("FONTSIZE", (-1, -1), (-1, -1), 20),
				("ALIGN", (0, 0), (-1, -1), "LEFT"),
				("VALIGN", (0, 0), (-1, -1), "TOP"),
				# ('BACKGROUND', (7, 0), (8, -1), colors.Color(0.74, 0.74, 0.74)),
				#
				# ('BACKGROUND', (6, 0), (6, -1), colors.Color(0.70, 0.70, 0.70)),
				# ('BACKGROUND', (9, 0), (9, -1), colors.Color(0.70, 0.70, 0.70)),
				#
				# ('BACKGROUND', (5, 0), (5, -1), colors.Color(0.65, 0.65, 0.65)),
				# ('BACKGROUND', (10, 0), (10, -1), colors.Color(0.65, 0.65, 0.65)),
				#
				# ('BACKGROUND', (4, 0), (4, -1), colors.Color(0.60, 0.60, 0.60)),
				# ('BACKGROUND', (11, 0), (11, -1), colors.Color(0.60, 0.60, 0.60)),
			]
		)

		headers = ("Dimension", "Mesure", "Score de Déviance", "Total/Composite")

		data = [headers]

		wanted_output = [
			("voice_a1", "voice"),
			("voice_b", "voice"),
			("voice_c", "voice"),
			("voice_d1", "voice"),
			("voice_a2", "voice"),
			("voice_d2", "voice"),
			("intell", None),
			("articulatory_accu", None),
			("max_phon_time", None),
			("speech_rate", None),
			("proso_contrast", None),
			("ddk_ccv_amr", "ddk_rate"),
			("ddk_cv_amr", "ddk_rate"),
			("ddk_cv_smr", "ddk_rate"),
			("ddk_smrcv_amrcv", "ddk_rate"),
		]

		line_id = 1
		for t in wanted_output:
			one_line = self.__generate_one_devscore_line__(t[0], t[1])
			data.append(one_line[0])
			if one_line[1] is not None and one_line[1] in colors_by_score:
				LIST_STYLE.add(
					"BACKGROUND",
					(2, line_id),
					(2, line_id),
					colors_by_score[one_line[1]],
				)
			else:
				LIST_STYLE.add(
					"BACKGROUND", (2, line_id), (3, line_id), colors_by_score[-1]
				)
			line_id += 1

		score_mp = IndicatorReporting.get_devscore_value("monpage_total")
		total_mp = IndicatorReporting.get_devscore_value("monpage_total_max")
		score_max = total_mp["score"]
		total_score_str = str(score_mp["score"])
		if score_max < 28:
			total_score_str = ">= " + str(score_mp["score"])
			LIST_STYLE.add(
				"BACKGROUND", (3, line_id), (3, line_id), colors_by_score[-1]
			)
		data.append(
			[
				"Score Total de Déviance (MonPaGe Version "
				+ GeneralTools.get_version()
				+ ")",
				"",
				"",
				total_score_str,
			]
		)

		LIST_STYLE.add("BOX", (0, 1), (-1, 6), 1, colors.black)

		LIST_STYLE.add("BOX", (0, 7), (-1, 7), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 7), (1, 7))

		LIST_STYLE.add("BOX", (0, 8), (-1, 8), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 8), (1, 8))

		LIST_STYLE.add("BOX", (0, 9), (-1, 9), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 9), (1, 9))

		LIST_STYLE.add("BOX", (0, 10), (-1, 10), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 10), (1, 10))

		LIST_STYLE.add("BOX", (0, 11), (-1, 11), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 11), (1, 11))

		LIST_STYLE.add("BOX", (0, 12), (-1, 15), 1, colors.black)
		LIST_STYLE.add("SPAN", (0, 12), (0, 15))
		LIST_STYLE.add("SPAN", (3, 12), (3, 15))
		LIST_STYLE.add("SPAN", (0, 1), (0, 6))
		LIST_STYLE.add("SPAN", (3, 1), (3, 6))
		LIST_STYLE.add("SPAN", (0, -1), (2, -1))
		LIST_STYLE.add("ALIGN", (-2, 1), (-1, -1), "CENTER")

		table = Table(
			data,
			colWidths=[40 * mm, 50 * mm, 40 * mm, 30 * mm],
			rowHeights=[None] * 16 + [15 * mm],
			spaceBefore=0 * mm,
		)
		table.setStyle(LIST_STYLE)
		self.elements.append(table)
		if 32 > total_mp["score"]:
			self.elements.append(
				Paragraph(
					'Attention, certains scores sont indiqués avec ">=" car il manque des valeurs, ils pourraient potentiellement être plus elevés',
					getSampleStyleSheet()["Normal"],
				)
			)

	@staticmethod
	def __generate_one_devscore_line__(devscore_code, composite_devscore_code):
		"""
		Generate a single row of development score data for the report table.

		Args:
			devscore_code (str): The code identifying the individual deviance score.
			composite_devscore_code (str or None): The code identifying the composite deviance score
				related to the individual score, if any.

		Returns:
			tuple:
				- list: Contains four elements:
					[group_name (str), label (str), score_ret (str), total_ret (str)]
					where:
						group_name: the group label of the deviance score,
						label: descriptive label of the score,
						score_ret: string representation of the individual score (e.g., "3/4" or "N/A"),
						total_ret: string representation of the composite score or fallback to individual score.
				- int or None: The numeric value of the individual deviance score, or None if unavailable.

		Behavior:
			- Retrieves the deviance score and composite deviance score via `IndicatorReporting`.
			- Formats scores as strings for display.
			- Handles incomplete composite scores by prefixing with ">= ".
			- Returns default values when data is missing.
			- Logs exceptions without raising.

		"""
		try:
			c_devs = IndicatorReporting.get_devscore_value(composite_devscore_code)
			devs = IndicatorReporting.get_devscore_value(devscore_code)
			score = None
			score_ret = "N/A"
			if devs is not None and devs["score"] is not None:
				# Simple score
				score_ret = str(devs["score"]) + "/4"
				score = devs["score"]
			if c_devs is not None and c_devs["score"] is not None:
				# Composite score
				if c_devs["complete"]:
					total_ret = str(float(c_devs["score"])) + "/6.0"
				else:
					total_ret = ">= " + str(float(c_devs["score"])) + "/6.0"
			else:
				# Composite column for simple score : we copy the value
				total_ret = score_ret
			group_name = ""
			if devs is not None and devs["group_label"] is not None:
				group_name = devs["group_label"]
			label = ""
			if devs is not None and devs["label"] is not None:
				label = devs["label"]

			return [group_name, label, score_ret, total_ret], score
		except Exception as e:
			logger.exception(e)

	def flush_file(self):
		"""
		Build and save the PDF report if there are elements to write.

		Returns:
			bool: True if the PDF was successfully built and saved, False otherwise.
		"""
		if len(self.elements) > 0:
			try:
				# self.doc.multiBuild(self.elements, canvasmaker=FooterHeaderCanvas)
				self.doc.build(
					self.elements,
					onFirstPage=self._header_footer,
					onLaterPages=self._header_footer,
				)
			# self.doc.build(self.elements)
			except (PermissionError, Exception) as e:
				UITools.error_log(e, "Erreur dans la creation du rapport pdf")
				return False

			return True
		else:
			return False

	def do_reporting(self):
		"""
		Generate the full PDF report including indicator and devscore tables,
		and write it to the output file.

		Returns:
			bool: True if the report was successfully created and saved, False otherwise.
		"""
		self.create_file()
		self.generate_indicator_table()
		self.elements.append(PageBreak())
		self.generate_devscore_table()
		return self.flush_file()

	@staticmethod
	def do_exporting(filename):
		"""
		Export percentile data to a given filename.

		Args:
			filename (str): Path to the export file.

		Returns:
			bool or result of IndicatorReporting.generate_percentile_datafile
		"""
		return IndicatorReporting.generate_percentile_datafile(filename)

	def _header_footer(self, canvas, doc):
		"""
		Draws a consistent header and footer on each page of the PDF document.

		Args:
			canvas: The canvas object from ReportLab used for drawing on the PDF page.
			doc: The document object (usually a ReportLab BaseDocTemplate or similar),
				representing the PDF being generated.

		This method is intended to be used as a page template callback during PDF generation.
		"""
		page = "%s" % canvas._pageNumber
		date = "Crée le %02d/%02d/%02d à %02d:%02d" % (
			datetime.now().day,
			datetime.now().month,
			datetime.now().year,
			datetime.now().hour,
			datetime.now().minute,
		)
		reporting = "Rapport MonPaGe v%s" % GeneralTools.get_version()
		sess_val = self.get_session_date_as_num()
		age = ParticipantManager.get_participant_age(sess_val[0])
		sess_date_format = "{}/{}/{}".format(sess_val[2], sess_val[1], sess_val[0])
		participant = (
			"Participant: {} | {} | session: {} (âge: {}) | examinat.: {}".format(
				self.participant_code,
				ParticipantManager.gender,
				sess_date_format,
				age,
				self.judge_code,
			)
		)
		canvas.saveState()
		canvas.setStrokeColorRGB(0, 0, 0)
		canvas.setLineWidth(0.5)
		canvas.setFont("Times-Roman", 10)

		# header
		canvas.line(
			1,
			LETTER[1] - self.header_height,
			LETTER[0] - 1,
			LETTER[1] - self.header_height,
		)
		canvas.drawString(10, LETTER[1] - self.header_height + 13, participant)
		canvas.drawString(
			LETTER[0] - 180, LETTER[1] - self.header_height + 13, reporting
		)

		# footer
		canvas.line(1, self.footer_height, LETTER[0] - 1, self.footer_height)
		canvas.drawString(LETTER[0] - 25, self.footer_height - 13, page)
		canvas.drawString(10, self.footer_height - 13, date)

		canvas.restoreState()
