"""
Profile Manager for Konica Minolta Ink Estimation Tool
Manages DeviceLink ICC profiles, HM44 4D LUTs, and calibration density curves.
"""

import os
import csv
import struct
import numpy as np

PRINT_MODES = {
    1: {"id": "RICH", "name": "Rich Color", "code": 1},
    2: {"id": "STD", "name": "Standard", "code": 2},
    3: {"id": "ECO", "name": "Economy / Ink Saving", "code": 3}
}

PAPER_TYPES = {
    1: {"id": "COATED", "name": "Coated Paper", "code": 1},
    2: {"id": "MATTE", "name": "Matte Coated", "code": 2},
    3: {"id": "WOODFREE", "name": "Woodfree / Uncoated", "code": 3}
}

QUALITY_MODES = {
    1: {"id": "NORMAL", "name": "Normal (Dither Screen)", "code": 1},
    2: {"id": "HD", "name": "High Definition (Error Diffusion)", "code": 2}
}


class ProfileData:
    def __init__(self, mode: int, media: int, quality: int, base_dir: str):
        self.mode = mode
        self.media = media
        self.quality = quality
        self.base_dir = base_dir

        self.icc_path = os.path.join(base_dir, "InkProcFolder", "exe", "ICC", "param", f"iccfile_{mode}_{media}_{quality}.icc")
        self.col_path = os.path.join(base_dir, "InkProcFolder", "exe", "ColorMatching", "param", f"ink_{mode}_{media}_{quality}.col")
        self.csv_path = os.path.join(base_dir, "InkProcFolder", "exe", "TotalINK", "param", f"totalink_{media}_{quality}.csv")

        # Fallback paths
        if not os.path.exists(self.icc_path):
            self.icc_path = os.path.join(base_dir, "InkProcFolder", "exe", "ICC", "param", "iccfile_1_1_1.icc")
        if not os.path.exists(self.col_path):
            self.col_path = os.path.join(base_dir, "InkProcFolder", "exe", "ColorMatching", "param", "ink_1_1_1.col")
        if not os.path.exists(self.csv_path):
            self.csv_path = os.path.join(base_dir, "InkProcFolder", "exe", "TotalINK", "param", "totalink_1_1.csv")

        # Load ICC DeviceLink CLUT
        self.has_icc = False
        self.icc_in_tables = None
        self.icc_clut = None
        self.icc_out_tables = None
        self.icc_grid_pts = 0
        self._load_icc()

        # Load HM44 LUT
        self.hm44_bytes = None
        self._load_hm44()

        # Load density curve
        self.curve = None
        self._load_curve()

    def _load_icc(self):
        if not os.path.exists(self.icc_path):
            return
        with open(self.icc_path, "rb") as f:
            raw = f.read()
        if len(raw) < 132:
            return

        tag_count = struct.unpack(">I", raw[128:132])[0]
        for i in range(tag_count):
            sig, offset, size = struct.unpack(">4sII", raw[132+i*12 : 132+(i+1)*12])
            if sig == b"A2B0":
                in_ch, out_ch, grid_pts = struct.unpack("BBB", raw[offset+8:offset+11])
                matrix_offset = offset + 12
                in_table_offset = matrix_offset + 36
                clut_offset = in_table_offset + in_ch * 256
                clut_size = (grid_pts ** in_ch) * out_ch
                out_table_offset = clut_offset + clut_size

                self.icc_in_tables = raw[in_table_offset:clut_offset]
                self.icc_clut = raw[clut_offset:out_table_offset]
                self.icc_out_tables = raw[out_table_offset:out_table_offset+out_ch*256]
                self.icc_grid_pts = grid_pts
                self.has_icc = True
                break

    def _load_hm44(self):
        if not os.path.exists(self.col_path):
            return
        with open(self.col_path, "rb") as f:
            f.read(4) # skip 'HM44' header
            self.hm44_bytes = f.read()

    def _load_curve(self):
        if not os.path.exists(self.csv_path):
            return
        rows = []
        with open(self.csv_path, "r", encoding="latin-1") as f:
            r = csv.reader(f)
            next(r) # skip header
            for row in r:
                if len(row) >= 5:
                    rows.append([float(x) for x in row[1:5]])
        if len(rows) == 256:
            self.curve = np.array(rows, dtype=np.float64)
        else:
            # Fallback linear if missing
            self.curve = np.zeros((256, 4), dtype=np.float64)


class ProfileManager:
    def __init__(self, base_dir: str = None):
        if base_dir is None:
            base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
        self.base_dir = base_dir
        self.cache = {}

    def get_profile(self, mode: int = 1, media: int = 1, quality: int = 1) -> ProfileData:
        key = (mode, media, quality)
        if key not in self.cache:
            self.cache[key] = ProfileData(mode, media, quality, self.base_dir)
        return self.cache[key]

    def list_options(self):
        return {
            "print_modes": list(PRINT_MODES.values()),
            "paper_types": list(PAPER_TYPES.values()),
            "quality_modes": list(QUALITY_MODES.values())
        }
