# ============================================================
# GUIDE COMPLET: MANIPULATION DE FICHIERS EN PYTHON
# Lecture, écriture et gestion complète des fichiers
# ============================================================

import os
import io
import sys
import mmap
import tempfile
import shutil
from pathlib import Path


# ============================================================
# 1. OUVERTURE DE FICHIERS - BASES
# ============================================================

# OPEN: Fonction principale pour ouvrir fichiers
file = open('file.txt', 'r')                # Mode lecture (défaut)
file = open('file.txt', 'w')                # Mode écriture (écrase)
file = open('file.txt', 'a')                # Mode ajout (append)
file = open('file.txt', 'x')                # Mode création exclusive
file = open('file.txt', 'r+')               # Lecture + écriture
file = open('file.txt', 'w+')               # Écriture + lecture (écrase)
file = open('file.txt', 'a+')               # Ajout + lecture

# MODE BINAIRE
file = open('image.png', 'rb')              # Lecture binaire
file = open('data.bin', 'wb')               # Écriture binaire
file = open('file.bin', 'ab')               # Ajout binaire
file = open('file.bin', 'rb+')              # Lecture/écriture binaire

# ENCODAGE
file = open('file.txt', 'r', encoding='utf-8')          # UTF-8 (recommandé)
file = open('file.txt', 'r', encoding='latin-1')        # ISO-8859-1
file = open('file.txt', 'r', encoding='cp1252')         # Windows
file = open('file.txt', 'r', encoding='ascii')          # ASCII

# GESTION DES FINS DE LIGNE
file = open('file.txt', 'r', newline='')    # Pas de conversion
file = open('file.txt', 'r', newline='\n')  # Unix
file = open('file.txt', 'r', newline='\r\n')# Windows

# BUFFERING
file = open('file.txt', 'r', buffering=-1)  # Défaut du système
file = open('file.txt', 'r', buffering=0)   # Non bufferisé (binaire seulement)
file = open('file.txt', 'r', buffering=1)   # Line buffered (texte)
file = open('file.txt', 'r', buffering=4096)# Buffer de 4KB

# ERRORS (gestion erreurs d'encodage)
file = open('file.txt', 'r', encoding='utf-8', errors='strict')      # Défaut: lève exception
file = open('file.txt', 'r', encoding='utf-8', errors='ignore')      # Ignore caractères invalides
file = open('file.txt', 'r', encoding='utf-8', errors='replace')     # Remplace par ?
file = open('file.txt', 'r', encoding='utf-8', errors='backslashreplace')  # \uXXXX

# CLOSE: Toujours fermer
file = open('file.txt', 'r')
# ... opérations ...
file.close()                                # Libère ressources


# ============================================================
# 2. CONTEXT MANAGER - BONNE PRATIQUE
# ============================================================

# WITH: Fermeture automatique
with open('file.txt', 'r') as file:
    content = file.read()
# Fichier automatiquement fermé ici

# WITH multiple
with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    content = infile.read()
    outfile.write(content.upper())

# WITH imbriqués (ancien style)
with open('input.txt', 'r') as infile:
    with open('output.txt', 'w') as outfile:
        outfile.write(infile.read())


# ============================================================
# 3. LECTURE DE FICHIERS
# ============================================================

# READ: Lire tout le contenu
with open('file.txt', 'r') as f:
    content = f.read()                      # Retourne string complète
    print(content)

# READ avec taille
with open('file.txt', 'r') as f:
    chunk = f.read(100)                     # Lit 100 caractères
    rest = f.read()                         # Lit le reste

# READ multiple (continue où arrêté)
with open('file.txt', 'r') as f:
    part1 = f.read(50)
    part2 = f.read(50)
    # etc.

# READLINE: Lire ligne par ligne
with open('file.txt', 'r') as f:
    line1 = f.readline()                    # Première ligne (avec \n)
    line2 = f.readline()                    # Deuxième ligne
    line3 = f.readline()                    # etc.

# READLINE avec taille
with open('file.txt', 'r') as f:
    partial_line = f.readline(10)           # Max 10 caractères de la ligne

# READLINES: Lire toutes les lignes
with open('file.txt', 'r') as f:
    lines = f.readlines()                   # Liste de strings
    # ['line1\n', 'line2\n', 'line3\n']

# READLINES avec hint (optimisation)
with open('file.txt', 'r') as f:
    lines = f.readlines(1000)               # Lit ~1000 bytes de lignes

# ITERATION: Méthode recommandée
with open('file.txt', 'r') as f:
    for line in f:                          # Ligne par ligne, mémoire efficace
        print(line.strip())                 # strip() enlève \n

# ITERATION avec enumerate
with open('file.txt', 'r') as f:
    for i, line in enumerate(f, start=1):
        print(f"Line {i}: {line.strip()}")

# LIST COMPREHENSION
with open('file.txt', 'r') as f:
    lines = [line.strip() for line in f]

# FILTRER LIGNES
with open('file.txt', 'r') as f:
    non_empty = [line.strip() for line in f if line.strip()]

# FICHIERS BINAIRES
with open('image.png', 'rb') as f:
    data = f.read()                         # bytes object
    print(type(data))                       # <class 'bytes'>


# ============================================================
# 4. ÉCRITURE DE FICHIERS
# ============================================================

# WRITE: Écrire string
with open('file.txt', 'w') as f:
    f.write('Hello World\n')                # Retourne nombre de caractères écrits
    f.write('Second line\n')

# WRITE multiple
with open('file.txt', 'w') as f:
    n1 = f.write('First\n')
    n2 = f.write('Second\n')
    total = n1 + n2                         # Total caractères écrits

# WRITELINES: Écrire liste de strings
with open('file.txt', 'w') as f:
    lines = ['line1\n', 'line2\n', 'line3\n']
    f.writelines(lines)                     # N'ajoute PAS \n automatiquement

# WRITELINES avec iterable
with open('file.txt', 'w') as f:
    f.writelines(f'{i}\n' for i in range(10))

# APPEND: Ajouter à la fin
with open('file.txt', 'a') as f:
    f.write('New line\n')                   # Ajoute à la fin

# PRINT vers fichier
with open('file.txt', 'w') as f:
    print('Hello', 'World', file=f)         # Utilise print()
    print('Line 2', file=f, end='!\n')

# ÉCRITURE BINAIRE
with open('data.bin', 'wb') as f:
    f.write(b'\x00\x01\x02\x03')            # bytes
    f.write(bytearray([4, 5, 6]))           # bytearray

# WRITE avec FLUSH
with open('file.txt', 'w') as f:
    f.write('Important data')
    f.flush()                               # Force écriture sur disque


# ============================================================
# 5. POSITION ET NAVIGATION
# ============================================================

# TELL: Position actuelle
with open('file.txt', 'r') as f:
    pos = f.tell()                          # Position en bytes
    print(f"Position: {pos}")

# SEEK: Déplacer curseur
with open('file.txt', 'r') as f:
    f.seek(0)                               # Début du fichier
    f.seek(10)                              # 10 bytes depuis début
    f.seek(0, 0)                            # os.SEEK_SET (depuis début)
    f.seek(5, 1)                            # os.SEEK_CUR (depuis position actuelle)
    f.seek(-10, 2)                          # os.SEEK_END (depuis fin)

# SEEK modes
import os
with open('file.txt', 'rb') as f:
    f.seek(0, os.SEEK_SET)                  # 0 = début
    f.seek(10, os.SEEK_CUR)                 # 1 = position actuelle
    f.seek(-5, os.SEEK_END)                 # 2 = fin

# RETOUR AU DÉBUT
with open('file.txt', 'r') as f:
    content1 = f.read()
    f.seek(0)                               # Retour début
    content2 = f.read()                     # Relit tout

# LIRE PORTION SPÉCIFIQUE
with open('file.txt', 'rb') as f:
    f.seek(100)                             # Va à byte 100
    chunk = f.read(50)                      # Lit 50 bytes

# OBTENIR TAILLE DU FICHIER
with open('file.txt', 'rb') as f:
    f.seek(0, os.SEEK_END)                  # Fin du fichier
    size = f.tell()                         # Taille en bytes
    f.seek(0)                               # Retour début


# ============================================================
# 6. PROPRIÉTÉS DES FICHIERS
# ============================================================

# ATTRIBUTS FILE OBJECT
with open('file.txt', 'r') as f:
    print(f.name)                           # Nom du fichier
    print(f.mode)                           # Mode d'ouverture ('r', 'w', etc.)
    print(f.encoding)                       # Encodage utilisé
    print(f.closed)                         # False (ouvert)
    print(f.newlines)                       # Types de fins de ligne vus
    print(f.errors)                         # Stratégie erreurs encodage

# CLOSED
f = open('file.txt', 'r')
print(f.closed)                             # False
f.close()
print(f.closed)                             # True

# FILENO: File descriptor
with open('file.txt', 'r') as f:
    fd = f.fileno()                         # Numéro du file descriptor
    print(fd)                               # 3, 4, etc.

# ISATTY: Est un terminal?
with open('file.txt', 'r') as f:
    is_terminal = f.isatty()                # False pour fichiers
    
print(sys.stdout.isatty())                  # True si terminal

# READABLE/WRITABLE/SEEKABLE
with open('file.txt', 'r+') as f:
    print(f.readable())                     # True si lecture possible
    print(f.writable())                     # True si écriture possible
    print(f.seekable())                     # True si seek possible


# ============================================================
# 7. TRUNCATE ET MODIFICATION
# ============================================================

# TRUNCATE: Tronquer fichier
with open('file.txt', 'r+') as f:
    f.truncate(100)                         # Garde premiers 100 bytes
    f.truncate()                            # Tronque à position actuelle

# EFFACER CONTENU
with open('file.txt', 'r+') as f:
    f.truncate(0)                           # Vide le fichier

# RÉÉCRIRE PORTION
with open('file.txt', 'r+') as f:
    f.seek(10)                              # Position 10
    f.write('NEW')                          # Écrase 3 caractères

# INSÉRER (nécessite réécriture)
def insert_at_position(filename, position, text):
    """Insère texte à position donnée"""
    with open(filename, 'r+') as f:
        content = f.read()
        new_content = content[:position] + text + content[position:]
        f.seek(0)
        f.write(new_content)
        f.truncate()


# ============================================================
# 8. FICHIERS TEMPORAIRES
# ============================================================

# TEMPORARYFILE: Fichier temporaire
with tempfile.TemporaryFile(mode='w+') as tmp:
    tmp.write('Temporary data')
    tmp.seek(0)
    print(tmp.read())
# Fichier automatiquement supprimé

# TEMPORARYFILE binaire
with tempfile.TemporaryFile(mode='wb+') as tmp:
    tmp.write(b'\x00\x01\x02')

# NAMEDTEMPORARYFILE: Avec nom accessible
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp:
    print(tmp.name)                         # Chemin du fichier
    tmp.write('data')
    tmp_path = tmp.name
# Fichier existe encore (delete=False)
os.remove(tmp_path)

# NAMEDTEMPORARYFILE avec suffix/prefix
with tempfile.NamedTemporaryFile(
    mode='w+',
    suffix='.txt',
    prefix='myapp_',
    dir='/tmp'
) as tmp:
    print(tmp.name)                         # /tmp/myapp_xyz123.txt

# TEMPORARYDIRECTORY: Répertoire temporaire
with tempfile.TemporaryDirectory() as tmpdir:
    print(tmpdir)                           # Chemin du répertoire
    file_path = os.path.join(tmpdir, 'file.txt')
    with open(file_path, 'w') as f:
        f.write('data')
# Répertoire et contenu supprimés

# MKSTEMP: Crée fichier temporaire sécurisé
fd, path = tempfile.mkstemp(suffix='.txt', prefix='tmp_')
try:
    with os.fdopen(fd, 'w') as f:
        f.write('secure data')
finally:
    os.remove(path)

# MKDTEMP: Crée répertoire temporaire
tmpdir = tempfile.mkdtemp(prefix='myapp_')
try:
    # Utiliser tmpdir
    pass
finally:
    shutil.rmtree(tmpdir)

# GETTEMPDIR: Répertoire temp du système
temp_dir = tempfile.gettempdir()            # /tmp sur Unix
print(temp_dir)


# ============================================================
# 9. FORMATS STRUCTURÉS - JSON
# ============================================================

import json

# JSON DUMP: Objet -> Fichier
data = {
    'name': 'Alice',
    'age': 30,
    'skills': ['Python', 'SQL']
}

with open('data.json', 'w') as f:
    json.dump(data, f)                      # Écrit JSON dans fichier

# DUMP avec options
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(
        data,
        f,
        indent=2,                           # Indentation
        ensure_ascii=False,                 # Permet caractères non-ASCII
        sort_keys=True                      # Trie les clés
    )

# JSON LOAD: Fichier -> Objet
with open('data.json', 'r') as f:
    data = json.load(f)                     # Parse JSON
    print(data['name'])

# DUMPS/LOADS: String au lieu de fichier
json_string = json.dumps(data, indent=2)
parsed = json.loads(json_string)

# CUSTOM ENCODER
class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

with open('data.json', 'w') as f:
    json.dump(data, f, cls=DateTimeEncoder)


# ============================================================
# 10. FORMATS STRUCTURÉS - CSV
# ============================================================

import csv

# CSV WRITER: Écrire CSV
with open('data.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Name', 'Age', 'City'])    # Header
    writer.writerow(['Alice', 30, 'Paris'])
    writer.writerow(['Bob', 25, 'Lyon'])

# WRITEROWS: Écrire plusieurs lignes
with open('data.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    rows = [
        ['Name', 'Age', 'City'],
        ['Alice', 30, 'Paris'],
        ['Bob', 25, 'Lyon']
    ]
    writer.writerows(rows)

# CSV READER: Lire CSV
with open('data.csv', 'r', newline='') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)                          # Liste de strings

# READER avec skip header
with open('data.csv', 'r') as f:
    reader = csv.reader(f)
    next(reader)                            # Skip header
    for row in reader:
        print(row)

# DICTWRITER: Avec dictionnaires
with open('data.csv', 'w', newline='') as f:
    fieldnames = ['name', 'age', 'city']
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    
    writer.writeheader()                    # Écrit header
    writer.writerow({'name': 'Alice', 'age': 30, 'city': 'Paris'})
    writer.writerow({'name': 'Bob', 'age': 25, 'city': 'Lyon'})

# DICTREADER: Lire en dictionnaires
with open('data.csv', 'r') as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row['name'], row['age'])      # Accès par clé

# CSV OPTIONS
with open('data.csv', 'w', newline='') as f:
    writer = csv.writer(
        f,
        delimiter=';',                      # Séparateur (défaut: ,)
        quotechar='"',                      # Caractère quote
        quoting=csv.QUOTE_MINIMAL           # Quand quoter
    )
    writer.writerow(['A', 'B;C', 'D'])      # B;C sera quoté

# QUOTING modes
# csv.QUOTE_MINIMAL    Quote si nécessaire
# csv.QUOTE_ALL        Quote tous les champs
# csv.QUOTE_NONNUMERIC Quote non-numériques
# csv.QUOTE_NONE       Pas de quotes

# DIALECT: Configuration réutilisable
csv.register_dialect('custom', delimiter='|', quoting=csv.QUOTE_ALL)
with open('data.csv', 'w', newline='') as f:
    writer = csv.writer(f, dialect='custom')
    writer.writerow(['A', 'B', 'C'])


# ============================================================
# 11. FORMATS STRUCTURÉS - PICKLE
# ============================================================

import pickle

# PICKLE DUMP: Sérialiser objet Python
data = {
    'list': [1, 2, 3],
    'dict': {'a': 1},
    'tuple': (1, 2, 3)
}

with open('data.pkl', 'wb') as f:
    pickle.dump(data, f)                    # Sérialise objet

# PICKLE LOAD: Désérialiser
with open('data.pkl', 'rb') as f:
    loaded = pickle.load(f)
    print(loaded)

# PICKLE MULTIPLE OBJECTS
with open('data.pkl', 'wb') as f:
    pickle.dump(obj1, f)
    pickle.dump(obj2, f)
    pickle.dump(obj3, f)

# Load multiple
with open('data.pkl', 'rb') as f:
    obj1 = pickle.load(f)
    obj2 = pickle.load(f)
    obj3 = pickle.load(f)

# PICKLE PROTOCOL (version)
with open('data.pkl', 'wb') as f:
    pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)

# DUMPS/LOADS: Bytes au lieu de fichier
pickled = pickle.dumps(data)                # bytes
unpickled = pickle.loads(pickled)

# CUSTOM PICKLING
class MyClass:
    def __getstate__(self):
        """Appelé lors du pickling"""
        return {'data': self.data}
    
    def __setstate__(self, state):
        """Appelé lors du unpickling"""
        self.data = state['data']


# ============================================================
# 12. FORMATS STRUCTURÉS - XML
# ============================================================

import xml.etree.ElementTree as ET

# PARSE XML
tree = ET.parse('data.xml')
root = tree.getroot()

# Accès éléments
for child in root:
    print(child.tag, child.attrib, child.text)

# FIND elements
element = root.find('item')                 # Premier item
elements = root.findall('item')             # Tous les items
element = root.find('.//nested/item')       # XPath

# CRÉER XML
root = ET.Element('root')
child = ET.SubElement(root, 'child', attrib={'id': '1'})
child.text = 'Content'

# WRITE XML
tree = ET.ElementTree(root)
tree.write('output.xml', encoding='utf-8', xml_declaration=True)

# PRETTY PRINT
import xml.dom.minidom
xml_str = ET.tostring(root, encoding='utf-8')
dom = xml.dom.minidom.parseString(xml_str)
pretty_xml = dom.toprettyxml(indent='  ')


# ============================================================
# 13. FORMATS STRUCTURÉS - YAML
# ============================================================

# Note: Nécessite PyYAML (pip install pyyaml)
try:
    import yaml
    
    # LOAD YAML
    with open('config.yaml', 'r') as f:
        config = yaml.safe_load(f)          # Parse YAML
    
    # DUMP YAML
    data = {
        'database': {
            'host': 'localhost',
            'port': 5432
        },
        'settings': ['a', 'b', 'c']
    }
    
    with open('config.yaml', 'w') as f:
        yaml.dump(data, f, default_flow_style=False)
    
    # LOAD ALL (multiple documents)
    with open('multi.yaml', 'r') as f:
        docs = yaml.safe_load_all(f)
        for doc in docs:
            print(doc)

except ImportError:
    print("PyYAML not installed: pip install pyyaml")


# ============================================================
# 14. FORMATS STRUCTURÉS - INI/CONFIG
# ============================================================

import configparser

# CRÉER CONFIG
config = configparser.ConfigParser()
config['DEFAULT'] = {'ServerAliveInterval': '45'}
config['Server'] = {
    'host': 'localhost',
    'port': '8080'
}

with open('config.ini', 'w') as f:
    config.write(f)

# LIRE CONFIG
config = configparser.ConfigParser()
config.read('config.ini')

# Accès valeurs
host = config['Server']['host']
port = config.getint('Server', 'port')      # Convertit en int
alive = config.getfloat('DEFAULT', 'ServerAliveInterval')

# OPTIONS
has_section = config.has_section('Server')
has_option = config.has_option('Server', 'host')
sections = config.sections()
options = config.options('Server')


# ============================================================
# 15. COMPRESSION - GZIP
# ============================================================

import gzip

# WRITE GZIP
with gzip.open('file.txt.gz', 'wt', encoding='utf-8') as f:
    f.write('Compressed text\n')

# READ GZIP
with gzip.open('file.txt.gz', 'rt', encoding='utf-8') as f:
    content = f.read()

# BINARY GZIP
with gzip.open('data.gz', 'wb') as f:
    f.write(b'Binary data')

# COMPRESSION LEVEL
with gzip.open('file.gz', 'wb', compresslevel=9) as f:
    f.write(b'Maximum compression')          # 1-9 (défaut: 9)

# COMPRESS/DECOMPRESS en mémoire
compressed = gzip.compress(b'Data to compress')
decompressed = gzip.decompress(compressed)


# ============================================================
# 16. COMPRESSION - ZIP
# ============================================================

import zipfile

# CRÉER ZIP
with zipfile.ZipFile('archive.zip', 'w') as zipf:
    zipf.write('file1.txt')                 # Ajoute fichier
    zipf.write('file2.txt', arcname='renamed.txt')  # Avec nom différent
    zipf.writestr('file3.txt', 'Content')   # Depuis string

# ZIP avec compression
with zipfile.ZipFile('archive.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
    zipf.write('file.txt')

# LIRE ZIP
with zipfile.ZipFile('archive.zip', 'r') as zipf:
    # Liste contenu
    names = zipf.namelist()
    
    # Info fichier
    info = zipf.getinfo('file1.txt')
    print(info.file_size, info.compress_size)
    
    # Lire fichier
    content = zipf.read('file1.txt')
    
    # Extraire tout
    zipf.extractall('output_dir')
    
    # Extraire un fichier
    zipf.extract('file1.txt', 'output_dir')

# APPEND à ZIP existant
with zipfile.ZipFile('archive.zip', 'a') as zipf:
    zipf.write('new_file.txt')

# ZIP DIRECTORY récursif
def zip_directory(directory, zip_path):
    """Compresse répertoire complet"""
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(directory):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, directory)
                zipf.write(file_path, arcname)


# ============================================================
# 17. COMPRESSION - AUTRES FORMATS
# ============================================================

import bz2
import lzma

# BZ2
with bz2.open('file.txt.bz2', 'wt', encoding='utf-8') as f:
    f.write('BZ2 compressed text')

with bz2.open('file.txt.bz2', 'rt', encoding='utf-8') as f:
    content = f.read()

# LZMA/XZ
with lzma.open('file.txt.xz', 'wt', encoding='utf-8') as f:
    f.write('LZMA compressed text')

with lzma.open('file.txt.xz', 'rt', encoding='utf-8') as f:
    content = f.read()

# TAR avec compression
import tarfile

# Créer tar.gz
with tarfile.open('archive.tar.gz', 'w:gz') as tar:
    tar.add('file1.txt')
    tar.add('directory', recursive=True)

# Extraire tar.gz
with tarfile.open('archive.tar.gz', 'r:gz') as tar:
    tar.extractall('output')
    
    # Liste membres
    for member in tar.getmembers():
        print(member.name)


# ============================================================
# 18. MEMORY-MAPPED FILES
# ============================================================

import mmap

# MMAP: Mapper fichier en mémoire
with open('large_file.bin', 'r+b') as f:
    mmapped = mmap.mmap(f.fileno(), 0)      # Map tout le fichier
    
    # Lecture
    data = mmapped[0:100]                   # Lit bytes 0-99
    
    # Écriture
    mmapped[0:5] = b'Hello'                 # Écrit à position 0
    
    # Recherche
    index = mmapped.find(b'pattern')
    
    # Fermeture
    mmapped.close()

# MMAP avec taille spécifique
with open('file.bin', 'r+b') as f:
    mmapped = mmap.mmap(f.fileno(), 1024)   # Map 1KB

# MMAP lecture seule
with open('file.bin', 'rb') as f:
    mmapped = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

# MMAP copy-on-write
with open('file.bin', 'rb') as f:
    mmapped = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY)


# ============================================================
# 19. FICHIERS BINAIRES AVANCÉS
# ============================================================

import struct

# STRUCT: Lire/écrire données binaires structurées
# Pack: Données -> Bytes
data = struct.pack('i', 12345)              # Integer 4 bytes
data = struct.pack('f', 3.14)               # Float 4 bytes
data = struct.pack('d', 3.14159)            # Double 8 bytes
data = struct.pack('5s', b'hello')          # String 5 bytes

# Format combiné
data = struct.pack('if5s', 42, 3.14, b'hello')

# Unpack: Bytes -> Données
value = struct.unpack('i', data)[0]

# WRITE structures binaires
with open('data.bin', 'wb') as f:
    # Header
    f.write(struct.pack('4sHH', b'DATA', 1, 0))  # Magic, version
    
    # Records
    for i in range(10):
        f.write(struct.pack('if', i, i * 3.14))

# READ structures binaires
with open('data.bin', 'rb') as f:
    # Header
    magic, major, minor = struct.unpack('4sHH', f.read(8))
    
    # Records
    while True:
        chunk = f.read(8)                   # int + float = 8 bytes
        if not chunk:
            break
        id_val, float_val = struct.unpack('if', chunk)

# CALCSIZE: Taille en bytes du format
size = struct.calcsize('if5s')              # 13 bytes


# ============================================================
# 20. STREAMING ET CHUNKS
# ============================================================

# LIRE PAR CHUNKS (gros fichiers)
def read_in_chunks(file_path, chunk_size=1024):
    """Lit fichier par morceaux"""
    with open(file_path, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

# Usage
for chunk in read_in_chunks('large_file.bin', 8192):
    process_chunk(chunk)

# COPIER avec chunks
def copy_file(src, dst, chunk_size=65536):
    """Copie fichier efficacement"""
    with open(src, 'rb') as fsrc:
        with open(dst, 'wb') as fdst:
            while True:
                chunk = fsrc.read(chunk_size)
                if not chunk:
                    break
                fdst.write(chunk)

# PROGRESS avec chunks
def copy_with_progress(src, dst):
    """Copie avec barre de progression"""
    size = os.path.getsize(src)
    copied = 0
    
    with open(src, 'rb') as fsrc:
        with open(dst, 'wb') as fdst:
            while True:
                chunk = fsrc.read(65536)
                if not chunk:
                    break
                fdst.write(chunk)
                copied += len(chunk)
                percent = (copied / size) * 100
                print(f'\rProgress: {percent:.1f}%', end='')
    print()

# GENERATOR pour lignes
def read_large_file(file_path):
    """Generator pour fichiers volumineux"""
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

# PROCESSING par batches
def process_in_batches(file_path, batch_size=1000):
    """Traite fichier par batches"""
    batch = []
    with open(file_path, 'r') as f:
        for line in f:
            batch.append(line.strip())
            if len(batch) >= batch_size:
                process_batch(batch)
                batch = []
        
        # Dernier batch
        if batch:
            process_batch(batch)


# ============================================================
# 21. FICHIERS SPÉCIAUX
# ============================================================

# STRINGIO: Fichier en mémoire (string)
from io import StringIO

sio = StringIO()
sio.write('Hello\n')
sio.write('World\n')
sio.seek(0)
content = sio.read()
sio.close()

# STRINGIO comme context manager
with StringIO() as sio:
    sio.write('Data')
    print(sio.getvalue())                   # Contenu complet

# BYTESIO: Fichier en mémoire (bytes)
from io import BytesIO

bio = BytesIO()
bio.write(b'\x00\x01\x02')
bio.seek(0)
data = bio.read()

# BYTESIO avec initial data
bio = BytesIO(b'Initial data')

# NULL FILE (discard output)
import os
with open(os.devnull, 'w') as f:
    print('This goes nowhere', file=f)


# ============================================================
# 22. LOCKING DE FICHIERS
# ============================================================

import fcntl  # Unix seulement

# EXCLUSIVE LOCK
with open('file.txt', 'r+') as f:
    fcntl.flock(f.fileno(), fcntl.LOCK_EX)  # Lock exclusif
    # Opérations sur fichier
    fcntl.flock(f.fileno(), fcntl.LOCK_UN)  # Unlock

# SHARED LOCK
with open('file.txt', 'r') as f:
    fcntl.flock(f.fileno(), fcntl.LOCK_SH)  # Lock partagé (lecture)
    data = f.read()
    fcntl.flock(f.fileno(), fcntl.LOCK_UN)

# NON-BLOCKING LOCK
with open('file.txt', 'r+') as f:
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        # Fichier locké
    except IOError:
        print("Fichier déjà locké")

# PORTABLE LOCKING (Windows + Unix)
import portalocker  # pip install portalocker

with open('file.txt', 'r+') as f:
    portalocker.lock(f, portalocker.LOCK_EX)
    # Opérations
    portalocker.unlock(f)


# ============================================================
# 23. ATOMIC WRITES
# ============================================================

# ATOMIC WRITE avec rename
def atomic_write(filepath, content):
    """Écriture atomique (évite corruption)"""
    import tempfile
    
    # Écrire dans fichier temporaire
    dir_path = os.path.dirname(filepath)
    fd, temp_path = tempfile.mkstemp(dir=dir_path)
    
    try:
        with os.fdopen(fd, 'w') as f:
            f.write(content)
            f.flush()
            os.fsync(f.fileno())
        
        # Rename atomique
        os.replace(temp_path, filepath)
    except:
        os.remove(temp_path)
        raise

# AVEC PATHLIB
def atomic_write_pathlib(filepath, content):
    """Version pathlib"""
    from pathlib import Path
    import tempfile
    
    path = Path(filepath)
    fd, temp_path = tempfile.mkstemp(
        dir=path.parent,
        prefix=path.name + '.',
        suffix='.tmp'
    )
    
    try:
        os.write(fd, content.encode())
        os.close(fd)
        os.replace(temp_path, filepath)
    except:
        os.close(fd)
        os.remove(temp_path)
        raise


# ============================================================
# 24. BACKUP ET ROTATION
# ============================================================

# BACKUP SIMPLE
def backup_file(filepath):
    """Crée backup avec timestamp"""
    import time
    import shutil
    
    if not os.path.exists(filepath):
        return None
    
    timestamp = time.strftime('%Y%m%d_%H%M%S')
    backup_path = f"{filepath}.{timestamp}.bak"
    shutil.copy2(filepath, backup_path)
    return backup_path

# ROTATION DE LOGS
def rotate_log(log_file, max_files=5):
    """Rotation de fichiers logs"""
    if not os.path.exists(log_file):
        return
    
    # Supprimer plus ancien
    oldest = f"{log_file}.{max_files}"
    if os.path.exists(oldest):
        os.remove(oldest)
    
    # Renommer fichiers existants
    for i in range(max_files - 1, 0, -1):
        old_name = f"{log_file}.{i}"
        new_name = f"{log_file}.{i + 1}"
        if os.path.exists(old_name):
            os.rename(old_name, new_name)
    
    # Renommer fichier actuel
    os.rename(log_file, f"{log_file}.1")


# ============================================================
# 25. MONITORING ET WATCHING
# ============================================================

# SIMPLE WATCHER
def watch_file(filepath, interval=1):
    """Surveille modifications de fichier"""
    import time
    
    last_mtime = os.path.getmtime(filepath)
    
    while True:
        time.sleep(interval)
        current_mtime = os.path.getmtime(filepath)
        
        if current_mtime != last_mtime:
            print(f"{filepath} has been modified")
            last_mtime = current_mtime
            # Réagir au changement
            handle_change(filepath)

# WATCHDOG (pip install watchdog)
try:
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    class MyHandler(FileSystemEventHandler):
        def on_modified(self, event):
            if not event.is_directory:
                print(f"Modified: {event.src_path}")
        
        def on_created(self, event):
            print(f"Created: {event.src_path}")
        
        def on_deleted(self, event):
            print(f"Deleted: {event.src_path}")
    
    # Usage
    def watch_directory(path):
        event_handler = MyHandler()
        observer = Observer()
        observer.schedule(event_handler, path, recursive=True)
        observer.start()
        
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            observer.stop()
        observer.join()

except ImportError:
    pass


# ============================================================
# 26. UTILITAIRES PRATIQUES
# ============================================================

# COMPTER LIGNES
def count_lines(filepath):
    """Compte lignes d'un fichier"""
    count = 0
    with open(filepath, 'r') as f:
        for line in f:
            count += 1
    return count

# COMPTER MOTS
def count_words(filepath):
    """Compte mots d'un fichier"""
    count = 0
    with open(filepath, 'r') as f:
        for line in f:
            count += len(line.split())
    return count

# TAIL (dernières N lignes)
def tail(filepath, n=10):
    """Retourne dernières N lignes"""
    with open(filepath, 'r') as f:
        lines = f.readlines()
        return lines[-n:]

# HEAD (premières N lignes)
def head(filepath, n=10):
    """Retourne premières N lignes"""
    lines = []
    with open(filepath, 'r') as f:
        for i, line in enumerate(f):
            if i >= n:
                break
            lines.append(line)
    return lines

# GREP (recherche pattern)
def grep(filepath, pattern):
    """Trouve lignes contenant pattern"""
    matches = []
    with open(filepath, 'r') as f:
        for i, line in enumerate(f, 1):
            if pattern in line:
                matches.append((i, line.strip()))
    return matches

# REPLACE in file
def replace_in_file(filepath, old, new):
    """Remplace toutes occurrences"""
    with open(filepath, 'r') as f:
        content = f.read()
    
    content = content.replace(old, new)
    
    with open(filepath, 'w') as f:
        f.write(content)

# MERGE FILES
def merge_files(input_files, output_file):
    """Fusionne plusieurs fichiers"""
    with open(output_file, 'w') as outf:
        for input_file in input_files:
            with open(input_file, 'r') as inf:
                outf.write(inf.read())

# SPLIT FILE
def split_file(filepath, num_parts):
    """Divise fichier en N parties"""
    size = os.path.getsize(filepath)
    chunk_size = size // num_parts
    
    with open(filepath, 'rb') as f:
        for i in range(num_parts):
            chunk = f.read(chunk_size if i < num_parts - 1 else -1)
            with open(f"{filepath}.part{i+1}", 'wb') as part:
                part.write(chunk)


# ============================================================
# 27. GESTION D'ERREURS
# ============================================================

# TRY/EXCEPT pour fichiers
try:
    with open('file.txt', 'r') as f:
        content = f.read()
except FileNotFoundError:
    print("Fichier introuvable")
except PermissionError:
    print("Permission refusée")
except IsADirectoryError:
    print("C'est un répertoire")
except UnicodeDecodeError:
    print("Erreur d'encodage")
except IOError as e:
    print(f"Erreur I/O: {e}")

# VÉRIFICATION EXISTENCE
if os.path.exists('file.txt'):
    with open('file.txt', 'r') as f:
        content = f.read()
else:
    print("Fichier n'existe pas")

# CRÉATION SI ABSENT
try:
    with open('file.txt', 'x') as f:
        f.write('Initial content')
except FileExistsError:
    print("Fichier existe déjà")

# FALLBACK
def safe_read(filepath, default=''):
    """Lecture avec valeur par défaut"""
    try:
        with open(filepath, 'r') as f:
            return f.read()
    except FileNotFoundError:
        return default


# ============================================================
# 28. BONNES PRATIQUES
# ============================================================

"""
[OK] BONNES PRATIQUES:

1. TOUJOURS UTILISER WITH
   - Fermeture automatique garantie
   - Même si exception levée

2. SPÉCIFIER ENCODAGE
   - encoding='utf-8' pour texte
   - Évite problèmes cross-platform

3. NEWLINE='' POUR CSV
   - Gestion correcte des fins de ligne
   - Sur tous les systèmes

4. CHUNKS POUR GROS FICHIERS
   - Ne pas charger tout en mémoire
   - Utiliser iteration ou read(chunk_size)

5. MODE BINAIRE POUR NON-TEXTE
   - Images, audio, binaires: mode 'b'
   - Pas d'encodage/décodage

6. ATOMIC WRITES
   - Écriture temp puis rename
   - Évite corruption si crash

7. VÉRIFIER EXISTENCE
   - try/except au lieu de if exists
   - Évite race conditions (EAFP)

8. BACKUP AVANT MODIFICATION
   - Copie de sécurité
   - Permet rollback

9. FLUSH ET FSYNC
   - Pour données critiques
   - Garantit écriture disque

10. NETTOYER RESSOURCES
    - Close FDs même en erreur
    - Libère handles système
"""


# ============================================================
# 29. ERREURS COURANTES
# ============================================================

# [X] ERREUR: Ne pas fermer fichier
f = open('file.txt', 'r')
content = f.read()
# [X] Fichier jamais fermé

# [OK] SOLUTION
with open('file.txt', 'r') as f:
    content = f.read()

# [X] ERREUR: Oublier encodage
with open('file.txt', 'r') as f:
    content = f.read()  # [X] Encodage système

# [OK] SOLUTION
with open('file.txt', 'r', encoding='utf-8') as f:
    content = f.read()

# [X] ERREUR: Charger gros fichier en mémoire
with open('huge.txt', 'r') as f:
    lines = f.readlines()  # [X] Tout en RAM

# [OK] SOLUTION
with open('huge.txt', 'r') as f:
    for line in f:  # [OK] Ligne par ligne
        process(line)

# [X] ERREUR: Mode incorrect
with open('file.txt', 'r') as f:
    f.write('data')  # [X] Pas en mode écriture

# [OK] SOLUTION
with open('file.txt', 'r+') as f:
    f.write('data')

# [X] ERREUR: Oublier newline='' pour CSV
with open('data.csv', 'w') as f:
    writer = csv.writer(f)  # [X] Peut doubler \r\n sur Windows

# [OK] SOLUTION
with open('data.csv', 'w', newline='') as f:
    writer = csv.writer(f)


# ============================================================
# 30. PERFORMANCE
# ============================================================

# BENCHMARK: read() vs readline() vs iteration
import time

def benchmark_reading(filepath):
    """Compare méthodes de lecture"""
    
    # Méthode 1: read() tout
    start = time.time()
    with open(filepath, 'r') as f:
        content = f.read()
    time1 = time.time() - start
    
    # Méthode 2: readline()
    start = time.time()
    with open(filepath, 'r') as f:
        while f.readline():
            pass
    time2 = time.time() - start
    
    # Méthode 3: iteration (recommandé)
    start = time.time()
    with open(filepath, 'r') as f:
        for line in f:
            pass
    time3 = time.time() - start
    
    print(f"read(): {time1:.3f}s")
    print(f"readline(): {time2:.3f}s")
    print(f"iteration: {time3:.3f}s (fastest)")

# BUFFERING optimal
# Buffer plus grand = moins d'appels système
with open('file.txt', 'r', buffering=65536) as f:
    content = f.read()

# SCANDIR vs listdir pour metadata
import os
import time

def compare_listing(directory):
    """Compare performance"""
    
    # listdir + stat
    start = time.time()
    files = []
    for name in os.listdir(directory):
        path = os.path.join(directory, name)
        size = os.path.getsize(path)
        files.append((name, size))
    time1 = time.time() - start
    
    # scandir (plus rapide)
    start = time.time()
    files = []
    with os.scandir(directory) as entries:
        for entry in entries:
            files.append((entry.name, entry.stat().st_size))
    time2 = time.time() - start
    
    print(f"listdir: {time1:.3f}s")
    print(f"scandir: {time2:.3f}s (faster)")


# ============================================================
# 31. FORMATS AVANCÉS - EXCEL
# ============================================================

# OPENPYXL (pip install openpyxl)
try:
    from openpyxl import Workbook, load_workbook
    
    # CRÉER EXCEL
    wb = Workbook()
    ws = wb.active
    ws.title = "Data"
    
    # Écrire données
    ws['A1'] = 'Name'
    ws['B1'] = 'Age'
    ws.append(['Alice', 30])
    ws.append(['Bob', 25])
    
    # Sauvegarder
    wb.save('data.xlsx')
    
    # LIRE EXCEL
    wb = load_workbook('data.xlsx')
    ws = wb.active
    
    # Lire cellule
    name = ws['A2'].value
    
    # Itérer sur lignes
    for row in ws.iter_rows(min_row=2, values_only=True):
        print(row)
    
    # Itérer sur colonnes
    for col in ws.iter_cols(min_col=1, max_col=2, values_only=True):
        print(col)

except ImportError:
    print("openpyxl not installed: pip install openpyxl")

# PANDAS pour Excel (pip install pandas openpyxl)
try:
    import pandas as pd
    
    # Écrire Excel
    df = pd.DataFrame({
        'Name': ['Alice', 'Bob'],
        'Age': [30, 25]
    })
    df.to_excel('data.xlsx', index=False, sheet_name='Data')
    
    # Lire Excel
    df = pd.read_excel('data.xlsx', sheet_name='Data')
    
    # Multiple sheets
    with pd.ExcelWriter('multi.xlsx') as writer:
        df1.to_excel(writer, sheet_name='Sheet1')
        df2.to_excel(writer, sheet_name='Sheet2')

except ImportError:
    print("pandas not installed: pip install pandas openpyxl")


# ============================================================
# 32. FORMATS AVANCÉS - PDF
# ============================================================

# PYPDF2 (pip install PyPDF2)
try:
    import PyPDF2
    
    # LIRE PDF
    with open('document.pdf', 'rb') as f:
        reader = PyPDF2.PdfReader(f)
        
        # Info
        num_pages = len(reader.pages)
        
        # Extraire texte
        page = reader.pages[0]
        text = page.extract_text()
        
        # Métadonnées
        metadata = reader.metadata
        print(metadata.title, metadata.author)
    
    # FUSIONNER PDFs
    merger = PyPDF2.PdfMerger()
    merger.append('doc1.pdf')
    merger.append('doc2.pdf')
    merger.write('merged.pdf')
    merger.close()
    
    # DIVISER PDF
    with open('document.pdf', 'rb') as f:
        reader = PyPDF2.PdfReader(f)
        
        # Extraire pages 1-3
        writer = PyPDF2.PdfWriter()
        for i in range(3):
            writer.add_page(reader.pages[i])
        
        with open('extract.pdf', 'wb') as out:
            writer.write(out)

except ImportError:
    print("PyPDF2 not installed: pip install PyPDF2")


# ============================================================
# 33. FORMATS AVANCÉS - IMAGES
# ============================================================

# PILLOW (pip install Pillow)
try:
    from PIL import Image
    
    # OUVRIR IMAGE
    img = Image.open('photo.jpg')
    
    # Info
    print(img.format, img.size, img.mode)
    
    # CONVERTIR format
    img.save('photo.png')
    
    # RESIZE
    img_resized = img.resize((800, 600))
    img_resized.save('photo_resized.jpg')
    
    # THUMBNAIL
    img.thumbnail((200, 200))
    img.save('thumbnail.jpg')
    
    # CROP
    box = (100, 100, 400, 400)
    cropped = img.crop(box)
    
    # ROTATE
    rotated = img.rotate(90)
    
    # FILTER
    from PIL import ImageFilter
    blurred = img.filter(ImageFilter.BLUR)
    
    # CONVERTIR mode
    grayscale = img.convert('L')            # Niveaux de gris
    rgb = img.convert('RGB')                # RGB

except ImportError:
    print("Pillow not installed: pip install Pillow")


# ============================================================
# 34. FORMATS AVANCÉS - MARKDOWN
# ============================================================

# MARKDOWN (pip install markdown)
try:
    import markdown
    
    # MD -> HTML
    with open('README.md', 'r') as f:
        md_text = f.read()
    
    html = markdown.markdown(md_text)
    
    with open('README.html', 'w') as f:
        f.write(html)
    
    # Avec extensions
    html = markdown.markdown(
        md_text,
        extensions=['tables', 'fenced_code', 'toc']
    )

except ImportError:
    print("markdown not installed: pip install markdown")


# ============================================================
# 35. SÉCURITÉ
# ============================================================

# PERMISSIONS SÉCURISÉES
def create_secure_file(filepath, content):
    """Crée fichier avec permissions restrictives"""
    # Ouvrir avec permissions 600 (rw-------)
    fd = os.open(filepath, os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0o600)
    try:
        os.write(fd, content.encode())
    finally:
        os.close(fd)

# VÉRIFIER CHEMIN (éviter directory traversal)
def safe_join(base_dir, user_path):
    """Joint chemins de manière sécurisée"""
    # Résoudre chemins
    base = os.path.abspath(base_dir)
    full = os.path.abspath(os.path.join(base, user_path))
    
    # Vérifier que full est sous base
    if not full.startswith(base + os.sep):
        raise ValueError("Path traversal attempt detected")
    
    return full

# SANITIZE FILENAME
import re

def sanitize_filename(filename):
    """Nettoie nom de fichier"""
    # Enlever caractères dangereux
    filename = re.sub(r'[^\w\s.-]', '', filename)
    # Pas de .. pour éviter traversal
    filename = filename.replace('..', '')
    # Limiter longueur
    filename = filename[:255]
    return filename

# HASH DE FICHIER
import hashlib

def file_hash(filepath, algorithm='sha256'):
    """Calcule hash d'un fichier"""
    h = hashlib.new(algorithm)
    
    with open(filepath, 'rb') as f:
        while True:
            chunk = f.read(65536)
            if not chunk:
                break
            h.update(chunk)
    
    return h.hexdigest()

# VÉRIFIER INTÉGRITÉ
def verify_file(filepath, expected_hash, algorithm='sha256'):
    """Vérifie intégrité avec hash"""
    actual = file_hash(filepath, algorithm)
    return actual == expected_hash


# ============================================================
# 36. LOGGING DANS FICHIERS
# ============================================================

import logging

# CONFIGURATION SIMPLE
logging.basicConfig(
    filename='app.log',
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logging.info('Application started')
logging.error('An error occurred')

# HANDLER PERSONNALISÉ
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)

# File handler
file_handler = logging.FileHandler('debug.log')
file_handler.setLevel(logging.DEBUG)

# Formatter
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)

logger.addHandler(file_handler)

# ROTATING FILE HANDLER
from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    'app.log',
    maxBytes=1024*1024,  # 1MB
    backupCount=5
)
logger.addHandler(handler)

# TIMED ROTATING HANDLER
from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',
    interval=1,
    backupCount=7
)
logger.addHandler(handler)


# ============================================================
# 37. ASYNC FILE I/O
# ============================================================

# AIOFILES (pip install aiofiles)
try:
    import asyncio
    import aiofiles
    
    async def async_read():
        """Lecture asynchrone"""
        async with aiofiles.open('file.txt', 'r') as f:
            content = await f.read()
            return content
    
    async def async_write():
        """Écriture asynchrone"""
        async with aiofiles.open('file.txt', 'w') as f:
            await f.write('Async content')
    
    async def async_readlines():
        """Lecture ligne par ligne async"""
        async with aiofiles.open('file.txt', 'r') as f:
            async for line in f:
                print(line.strip())
    
    # Exécution
    # asyncio.run(async_read())

except ImportError:
    print("aiofiles not installed: pip install aiofiles")


# ============================================================
# 38. PATTERNS AVANCÉS
# ============================================================

# CONTEXT MANAGER PERSONNALISÉ
class FileManager:
    """Context manager pour fichier avec logging"""
    
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"Opening {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            print(f"Closing {self.filename}")
            self.file.close()
        return False

# Usage
with FileManager('file.txt', 'r') as f:
    content = f.read()

# DECORATOR pour file operations
def with_file(filename, mode='r'):
    """Decorator qui passe file object à fonction"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            with open(filename, mode) as f:
                return func(f, *args, **kwargs)
        return wrapper
    return decorator

@with_file('data.txt', 'r')
def process_file(f):
    return f.read().upper()

# CACHING de fichiers
from functools import lru_cache
import hashlib

@lru_cache(maxsize=128)
def cached_read(filepath, mtime):
    """Cache contenu basé sur mtime"""
    with open(filepath, 'r') as f:
        return f.read()

def read_with_cache(filepath):
    """Lit fichier avec cache"""
    mtime = os.path.getmtime(filepath)
    return cached_read(filepath, mtime)

# LAZY LOADING
class LazyFile:
    """Charge fichier seulement si nécessaire"""
    
    def __init__(self, filepath):
        self.filepath = filepath
        self._content = None
    
    @property
    def content(self):
        if self._content is None:
            with open(self.filepath, 'r') as f:
                self._content = f.read()
        return self._content


# ============================================================
# 39. EXEMPLES COMPLETS
# ============================================================

# LOG PARSER
def parse_log_file(log_file):
    """Parse fichier log et extrait statistiques"""
    stats = {
        'total': 0,
        'errors': 0,
        'warnings': 0,
        'info': 0
    }
    
    with open(log_file, 'r') as f:
        for line in f:
            stats['total'] += 1
            if 'ERROR' in line:
                stats['errors'] += 1
            elif 'WARNING' in line:
                stats['warnings'] += 1
            elif 'INFO' in line:
                stats['info'] += 1
    
    return stats

# CSV TRANSFORMER
def transform_csv(input_file, output_file, transform_func):
    """Applique transformation sur chaque ligne CSV"""
    with open(input_file, 'r') as inf, open(output_file, 'w', newline='') as outf:
        reader = csv.DictReader(inf)
        writer = csv.DictWriter(outf, fieldnames=reader.fieldnames)
        
        writer.writeheader()
        for row in reader:
            transformed = transform_func(row)
            writer.writerow(transformed)

# FILE DIFF
def diff_files(file1, file2):
    """Compare deux fichiers ligne par ligne"""
    import difflib
    
    with open(file1, 'r') as f1, open(file2, 'r') as f2:
        lines1 = f1.readlines()
        lines2 = f2.readlines()
    
    diff = difflib.unified_diff(
        lines1, lines2,
        fromfile=file1,
        tofile=file2
    )
    
    return ''.join(diff)

# BATCH PROCESSOR
def batch_process_files(directory, pattern, process_func):
    """Traite tous fichiers matchant pattern"""
    import glob
    
    files = glob.glob(os.path.join(directory, pattern))
    results = []
    
    for filepath in files:
        try:
            result = process_func(filepath)
            results.append((filepath, 'success', result))
        except Exception as e:
            results.append((filepath, 'error', str(e)))
    
    return results

# CONFIG MANAGER
class ConfigManager:
    """Gestion de fichier configuration"""
    
    def __init__(self, config_file):
        self.config_file = config_file
        self.config = {}
        self.load()
    
    def load(self):
        """Charge configuration"""
        if os.path.exists(self.config_file):
            with open(self.config_file, 'r') as f:
                self.config = json.load(f)
    
    def save(self):
        """Sauvegarde configuration"""
        with open(self.config_file, 'w') as f:
            json.dump(self.config, f, indent=2)
    
    def get(self, key, default=None):
        """Récupère valeur"""
        return self.config.get(key, default)
    
    def set(self, key, value):
        """Définit valeur"""
        self.config[key] = value
        self.save()


# ============================================================
# 40. RESSOURCES
# ============================================================

"""
DOCUMENTATION OFFICIELLE:
- https://docs.python.org/3/library/io.html
- https://docs.python.org/3/tutorial/inputoutput.html

MODULES STANDARDS:
- io: Core I/O functionality
- os: OS-level file operations
- pathlib: Object-oriented paths
- shutil: High-level file operations
- tempfile: Temporary files
- csv: CSV files
- json: JSON format
- pickle: Python object serialization
- configparser: INI files
- gzip, bz2, lzma: Compression
- zipfile, tarfile: Archives
- mmap: Memory-mapped files
- struct: Binary data structures

BIBLIOTHÈQUES TIERCES:
- pandas: Data analysis (Excel, CSV)
- openpyxl: Excel files
- PyPDF2: PDF manipulation
- Pillow: Image processing
- aiofiles: Async file I/O
- watchdog: File system monitoring
- portalocker: Cross-platform file locking

MODES D'OUVERTURE:
r   : Lecture (défaut)
w   : Écriture (écrase)
a   : Ajout (append)
x   : Création exclusive
r+  : Lecture + écriture
w+  : Écriture + lecture (écrase)
a+  : Ajout + lecture
b   : Mode binaire (rb, wb, etc.)
t   : Mode texte (défaut)

ENCODAGES COURANTS:
- utf-8: Unicode (recommandé)
- ascii: ASCII 7-bit
- latin-1: ISO-8859-1
- cp1252: Windows Western European
- utf-16: Unicode 16-bit
- utf-32: Unicode 32-bit

CONCEPTS CLÉS:
1. Context Managers: Gestion automatique ressources
2. Buffering: Optimisation I/O
3. Encoding: Conversion bytes <-> text
4. Seeking: Navigation dans fichier
5. Binary vs Text: Modes de lecture
6. Atomicity: Opérations indivisibles
7. Locking: Synchronisation multi-process

RÈGLES D'OR:
[OK] Toujours utiliser 'with'
[OK] Spécifier encodage pour texte
[OK] Mode binaire pour non-texte
[OK] Chunks pour gros fichiers
[OK] Vérifier exceptions
[OK] Backup avant modification
[OK] Permissions sécurisées
[OK] Chemins absolus
[OK] Atomic writes pour critique
[OK] Fermer ressources proprement
"""

# Helpers pour exemples
def process_chunk(chunk):
    """Placeholder pour traitement"""
    pass

def process(line):
    """Placeholder pour traitement"""
    pass

def handle_change(filepath):
    """Placeholder pour changement"""
    pass

def process_batch(batch):
    """Placeholder pour batch"""
    pass

# Si exécuté comme script
if __name__ == '__main__':
    print("Guide complet manipulation fichiers Python")
    print("Voir code source pour tous les exemples")
