from __future__ import unicode_literals
from zipfile import ZipFile
import decimal
import datetime
from xml.dom.minidom import parseString

from . import ods_components
from .formula import Formula

# --- Compatibilité Python 2/3 ---
try:
    long
except NameError:
    long = int

try:
    unicode
except NameError:
    unicode = str
# --- Fin compatibilité ---


class ODSWriter(object):
    """
    Utility for writing OpenDocument Spreadsheets.
    Can be used as a context manager:
        with ods.writer(open("fichier.ods", "wb")) as odsfile:
            odsfile.writerow(["A", "B"])
    """

    def __init__(self, odsfile):
        self.zipf = ZipFile(odsfile, "w")
        self.dom = parseString(ods_components.content_xml)
        # Fichiers de base de l'ODS
        self.zipf.writestr("mimetype", ods_components.mimetype.encode("utf-8"))
        self.zipf.writestr("META-INF/manifest.xml", ods_components.manifest_xml.encode("utf-8"))
        self.zipf.writestr("styles.xml", ods_components.styles_xml.encode("utf-8"))
        self.default_sheet = None
        self.sheets = []
        self._closed = False

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Ferme proprement le fichier, même si une exception est levée."""
        try:
            self.close()
        except Exception:
            # On ignore les erreurs de fermeture pour éviter ResourceWarning
            pass

    def close(self):
        """
        Finalise le document et ferme le conteneur ZIP proprement.
        Peut être appelé plusieurs fois sans effet ni warning.
        """
        if self._closed:
            return
        try:
            # Écrire le contenu XML final
            self.zipf.writestr("content.xml", self.dom.toxml().encode("utf-8"))
        except Exception:
            # On ignore les erreurs d’écriture si le zip est déjà fermé
            pass
        finally:
            try:
                if getattr(self, "zipf", None):
                    self.zipf.close()
            except Exception:
                pass
            self._closed = True

    def writerow(self, cells):
        """Écrit une ligne de cellules dans la feuille par défaut."""
        if self.default_sheet is None:
            self.default_sheet = self.new_sheet()
        self.default_sheet.writerow(cells)

    def writerows(self, rows):
        """Écrit plusieurs lignes dans la feuille par défaut."""
        for row in rows:
            self.writerow(row)

    def new_sheet(self, name=None, cols=None):
        """Crée une nouvelle feuille dans le document et la renvoie."""
        sheet = Sheet(self.dom, name, cols)
        self.sheets.append(sheet)
        return sheet


class Sheet(object):
    def __init__(self, dom, name="Sheet 1", cols=None):
        self.dom = dom
        self.cols = cols
        spreadsheet = self.dom.getElementsByTagName("office:spreadsheet")[0]
        self.table = self.dom.createElement("table:table")
        if name:
            self.table.setAttribute("table:name", name)
        self.table.setAttribute("table:style-name", "ta1")

        if self.cols is not None:
            col = self.dom.createElement("table:table-column")
            col.setAttribute("table:number-columns-repeated", unicode(self.cols))
            self.table.appendChild(col)

        spreadsheet.appendChild(self.table)

    def writerow(self, cells):
        row = self.dom.createElement("table:table-row")
        content_cells = len(cells)

        if self.cols is not None:
            padding_cells = self.cols - content_cells
            if content_cells > self.cols:
                raise Exception("More cells than cols.")
            cells += [None] * padding_cells

        for cell_data in cells:
            cell = self.dom.createElement("table:table-cell")
            text = None

            if isinstance(cell_data, (datetime.date, datetime.datetime)):
                cell.setAttribute("office:value-type", "date")
                date_str = cell_data.isoformat()
                cell.setAttribute("office:date-value", date_str)
                cell.setAttribute("table:style-name", "cDateISO")
                text = date_str

            elif isinstance(cell_data, datetime.time):
                cell.setAttribute("office:value-type", "time")
                cell.setAttribute("office:time-value", cell_data.strftime("PT%HH%MM%SS"))
                cell.setAttribute("table:style-name", "cTime")
                text = cell_data.strftime("%H:%M:%S")

            elif isinstance(cell_data, bool):
                cell.setAttribute("office:value-type", "boolean")
                cell.setAttribute("office:boolean-value", "true" if cell_data else "false")
                cell.setAttribute("table:style-name", "cBool")
                text = "TRUE" if cell_data else "FALSE"

            elif isinstance(cell_data, (float, int, decimal.Decimal, long)):
                cell.setAttribute("office:value-type", "float")
                float_str = unicode(cell_data)
                cell.setAttribute("office:value", float_str)
                text = float_str

            elif isinstance(cell_data, Formula):
                cell.setAttribute("table:formula", str(cell_data))

            elif cell_data is None:
                pass  # cellule vide

            else:
                cell.setAttribute("office:value-type", "string")
                text = unicode(cell_data)

            if text:
                p = self.dom.createElement("text:p")
                p.appendChild(self.dom.createTextNode(text))
                cell.appendChild(p)

            row.appendChild(cell)

        self.table.appendChild(row)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)


def writer(odsfile, *args, **kwargs):
    """
    Retourne un ODSWriter utilisable comme gestionnaire de contexte :
        with open("fichier.ods", "wb") as f:
            with ods.writer(f) as odsfile:
                odsfile.writerow(["Nom", "Valeur"])
    """
    return ODSWriter(odsfile, *args, **kwargs)
