# coding: utf-8
#
#    Project: CCD mask image reader/writer. Format used in CrysalisPro software
#             https://github.com/silx-kit/fabio
#
#    Copyright (C) European Synchrotron Radiation Facility, Grenoble, France
#
#  Permission is hereby granted, free of charge, to any person
#  obtaining a copy of this software and associated documentation files
#  (the "Software"), to deal in the Software without restriction,
#  including without limitation the rights to use, copy, modify, merge,
#  publish, distribute, sublicense, and/or sell copies of the Software,
#  and to permit persons to whom the Software is furnished to do so,
#  subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be
#  included in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
#  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
#  OTHER DEALINGS IN THE SOFTWARE.

"""The CCD fileformat is used by the CrysalisPro software (provided for free by Rigaku)
The main developper of CrysalisPro is Mathias Meyer. Thanks to him for providing the structure of the file.
This file contains the code to generate a mask-file.
"""

__authors__ = ["Jérôme Kieffer"]
__contact__ = "jerome.kieffer@esrf.eu"
__license__ = "MIT"
__copyright__ = "2022 ESRF"
__date__ = "03/03/2023"

import logging
logger = logging.getLogger(__name__)
import time
import os
import numpy
from .fabioimage import FabioImage
from dataclasses import dataclass
from enum import Enum
import struct
# Some constants used in the file:
PAR_TEMPLATE = """#XCALIBUR SYSTEM
#XCALIBUR PARAMETER FILE - GENERATED BY THE COMMANDS WD PAR OR WD CAL
#THIS FILE SHOULD NOT BE EDITED BY HAND - USE THE PROGRAM KM4SETUP OR MODIFY PARS IN THE PROGRAM
#**********************************************************************************************
# D A T E  {date}
#**********************************************************************************************
#CELL INFORMATION
#      5.96002 (    0.00100 )     9.04296 (    0.00138 )    18.39626 (    0.00266 )
#     89.99769 (    0.01211 )    90.00650 (    0.01261 )    90.02161 (    0.01297 )
#  V =       991.49
#PROGRAM CRYSALIS 41.122a 64-bit (release 06-12-2021)
#MACHINE INFORMATION
#    - GONIOMETER KUMA/OXFORD INSTRUMENTS/OXFORD DIFFRACTION KM4
#   - ALPHA (DEG)   {alpha} BETA (DEG)    {beta}
#    - WAVELENGTH USER (ANG): A1    {wavelength} A2    {wavelength} B1    {wavelength}
#   - POLARISATION FACTOR    {polarization}#   - X-RAY BEAM ORIENTATION (DEG): X2   -0.01932 X3    0.00000
#   - BEAM PATH ABSORPTIONCOEFFICIENTS (MM-1): AIR    0.00013 BE    0.04736
#   - BE THICKNESS (MM):    0.50000
#   - SOFTWARE ZEROCORRECTION (DEG): OMEGA 0.04303 THETA -0.32229 KAPPA 0.00000 PHI 0.00000
#   - MACHINE OFFSETS (STEPS): OMEGA          0 THETA          0 KAPPA          0 PHI          0
#- CCD DETECTOR KM4CCD/SAPPHIRE
#   - CHIP: n/a n/a ({shape_x}x{shape_y}) n/a
#   - TAPER:  (,),
#            ZOOM 0.000000 PIXELSIZE IN 1X1 BINNING (MM)  0.06000
#   - GAIN 15.00 (DOUBLE-CORRELATED-DIVIDE-BY-2 GAIN)
#   - DETECTOR ROTATION (DEG): X1    0.00000 X2    0.00000 X3    0.00000
#   - DETECTOR DISTANCE (MM):   {distance}
#   - DETECTOR ZERO (PIX, 1X1 BINNING): X  512 Y  512
#   - DETECTOR BINNING (PIX): X:   1 Y:   1
#   - SETUP PATH : {path}
#   - GEOMETRIC CORRECTION FILE : NONE
#   - FLAT FIELD CORRECTION FILE: NONE
#   - GEOMETRIC CORRECTION STATUS: ALLOCATED:0, USED:0, GRIDX:0, GRIDY:0
#   - GEOMETRIC CORRECTION GRID LOADED: NONE
# CCD CHIPCONSTANTS systemgain darksignal readnoiserms overflowthreshold
CCD CHIPCONSTANTS 1.0 0.500 8.00 1000000
# CCD DARKACQUISITION numofdarkimages smpo_numofdarkimages type time
CCD DARKACQUISITION 4 2 DARK_TRIGGER_REF 60
# CCD DISPLAYOFFSET offset
CCD DISPLAYOFFSET       60
# CCD DISPLAYSHIFT shift
CCD DISPLAYSHIFT        -4
# CCD ISACARDSETTINGS iosettingno memsettingno interruptno
CCD ISACARDSETTINGS 3 2 5
# CCD PARAMETERS bx by x1 y1 xw yw dark flood spike readnode mpp gain
CCD PARAMETERS 1 1 0 0 {shape_x} {shape_y} DARKOFF FLOODOFF SPIKECORRELATION READNODEAD FIP60 GAINBOTH
# CCD VISUALIZATION lookuptablename colortablename
CCD VISUALIZATION LUT1 MORNINGGLORY
# CRYSTALLOGRAPHY ALPHAANGLEINDEG alpha alphapd delta deltapd
CRYSTALLOGRAPHY ALPHAANGLEINDEG   50.00000   50.0000   90.00000   90.00000
# CRYSTALLOGRAPHY BETAANGLEINDEG beta betapd gamma gammapd
CRYSTALLOGRAPHY BETAANGLEINDEG   0.00000   0.0000   90.00000   90.00000
# CRYSTALLOGRAPHY DATAREDUCTION C1 C2 LLthresh searchwinhalf LLprofpts LLstart LLback minwin ibackframes
CRYSTALLOGRAPHY DATAREDUCTION    2.50000    0.00000    6.00000     2    30     1     1     3     5
# CRYSTALLOGRAPHY DATRED3D #harvestref inxwin inywin
CRYSTALLOGRAPHY DATRED3D 1000 48 48
# CRYSTALLOGRAPHY EWALDEXPLORERCAMERA frx fry frz tox toy toz upx upy upz aph apv zo front back
CRYSTALLOGRAPHY EWALDEXPLORERCAMERA 1.0 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 2.0 15.0 15.0 0.2 0 3
# CRYSTALLOGRAPHY EWALDEXPLORERSCREEN centerh centerv sizeh sizev
CRYSTALLOGRAPHY EWALDEXPLORERSCREEN 255 255 512 512
# CRYSTALLOGRAPHY NIGGLINUM ordinal niggli
CRYSTALLOGRAPHY NIGGLINUM 0 35
# CRYSTALLOGRAPHY NIGGLINUM ordinal niggli
CRYSTALLOGRAPHY NIGGLINUM 1 31
# CRYSTALLOGRAPHY NUMOFCELLS num
CRYSTALLOGRAPHY NUMOFCELLS        2
# CRYSTALLOGRAPHY NUMOFCELLSELECTED num
CRYSTALLOGRAPHY NUMOFCELLSELECTED        1
# CRYSTALLOGRAPHY ORIENTATIONMATRIX ub11 ub12 ub13 ... ub33 sub11 sub12 sub13 ... sub33
CRYSTALLOGRAPHY ORIENTATIONMATRIX -1.6894770E-002 -7.3228995E-002 -4.2883594E-002 7.9930593E-002 -2.0799582E-003 -3.9145127E-003 1.4021523E-002 -7.6538277E-002 4.1110883E-002 1.3594009E-005 1.9420319E-005 8.9086477E-006 1.2428387E-005 1.7755118E-005 8.1447731E-006 1.3554416E-005 1.9363756E-005 8.8827005E-006
# CRYSTALLOGRAPHY PEAKHUNTINGSCANWIDTH C1 C2
CRYSTALLOGRAPHY PEAKHUNTINGSCANWIDTH    1.00000    0.00000
# CRYSTALLOGRAPHY PEAKLOCATIONTABLEENTRIES numofpeaks
CRYSTALLOGRAPHY PEAKLOCATIONTABLEENTRIES    1000000
# CRYSTALLOGRAPHY PEAKTABLEENTRIES numofpeaks
CRYSTALLOGRAPHY PEAKTABLEENTRIES     1000000
# CRYSTALLOGRAPHY REDFORM r1 r2 r3 r4 r5 r6
CRYSTALLOGRAPHY REDFORM   8.90584865699E+001   1.51732728652E+002   2.94091358333E+002   4.22511681824E+001   1.50500907943E-001   8.68068266216E-003
# CRYSTALLOGRAPHY SIGMAREDFORM sr1 sr2 sr3 sr4 sr5 sr6
CRYSTALLOGRAPHY SIGMAREDFORM   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000
# CRYSTALLOGRAPHY SIGMAUB sub11 sub12 sub13 sub21 sub22 sub23 sub31 sub32 sub33
CRYSTALLOGRAPHY SIGMAUB   1.84797091592E-010   3.77148797034E-010   7.93640042416E-011   1.54464804286E-010   3.15244220669E-010   6.63373285638E-011   1.83722182099E-010   3.74955035116E-010   7.89023675307E-011
# CRYSTALLOGRAPHY SIGMAUM sum11 sum12 sum13 sum21 sum22 sum23 sum31 sum32 sum33
CRYSTALLOGRAPHY SIGMAUM   4.30291094497E-012   4.26333622558E-012   1.11470710805E-012   4.26333622558E-012   1.68813835518E-011   2.22021542351E-012   1.11470710805E-012   2.22021542351E-012   1.12128155741E-012
# CRYSTALLOGRAPHY UB ub11 ub12 ub13 ub21 ub22 ub23 ub31 ub32 ub33
CRYSTALLOGRAPHY UB  -1.68947699218E-002  -7.32289948389E-002  -4.28835937346E-002   7.99305932828E-002  -2.07995820653E-003  -3.91451274126E-003   1.40215232844E-002  -7.65382772048E-002   4.11108832953E-002
# CRYSTALLOGRAPHY UD ud11 ud12 ud13 ud21 ud22 ud23 ud31 ud32 ud33
CRYSTALLOGRAPHY UD   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000   0.00000000000E+000
# CRYSTALLOGRAPHY UM um11 um12 um13 um21 um22 um23 um31 um32 um33
CRYSTALLOGRAPHY UM   6.87093610846E-003  -2.24851001684E-006   9.88056331109E-004  -2.24851001684E-006   1.12249197887E-002   1.90830537823E-006   9.88056331109E-004   1.90830537823E-006   3.54443074691E-003
# CRYSTALLOGRAPHY UPDATECELLDATA
CRYSTALLOGRAPHY UPDATECELLDATA
# CRYSTALLOGRAPHY WAVELENGTH type
CRYSTALLOGRAPHY WAVELENGTH 0.373800 0.373800 0.373800
# DETECTOR CHARACTERISTICS offsetdeg deadtime filter
DETECTOR CHARACTERISTICS 17.0 0.00001 6.75
# EXPERIMENT COLLIMATOR len dia type
EXPERIMENT COLLIMATOR 140 0.5 PINHOLE
# EXPERIMENT COMPOUNDNAME compoundname
EXPERIMENT COMPOUNDNAME "compound name"
# EXPERIMENT GENERATOR kV mA type
EXPERIMENT GENERATOR     50.000     45.000 SEALEDTUBE
# EXPERIMENT TEMPERATURE tempinK
EXPERIMENT TEMPERATURE    293.000
# EXPERIMENT USER name
EXPERIMENT USER "Dr. User Name"
# FILE CHIP filename
FILE CHIP "{ccd_file}"
# FILE FLOOD filename
FILE FLOOD "NONE"
# FILE GEO filename
FILE GEO "NONE"
# FILE VERSION versionmajor versionminor versionstring
FILE VERSION 41 122 "41.122a 64-bit (release 06-12-2021)"
# GEOMETRYCORRECTION GRIDBASE e1xmm e1ymm e2xmm e2ymm offsetxmm offsetymm rotangledeg
GEOMETRYCORRECTION GRIDBASE 0.508 0.0 0.0 0.508 0.0 0.0 2.0
# GEOMETRYCORRECTION SEARCHWINDOW basewidthpix widthpix
GEOMETRYCORRECTION SEARCHWINDOW 12 12
# GONIOMETER ANGLE_LOLIM omlolimdeg thlolimdeg kalolimdeg phlolimdeg
GONIOMETER ANGLE_LOLIM  -300.00   -93.00  -179.00  -360.00
# GONIOMETER ANGLE_RANGEO omrangeodeg thrangeodeg karangeodeg phrangeodeg
GONIOMETER ANGLE_RANGEO 0 0 0 0
# GONIOMETER ANGLE_UPLIM omuplimdeg thuplimdeg kauplimdeg phuplimdeg
GONIOMETER ANGLE_UPLIM   300.00   120.00   179.00   360.00
# GONIOMETER COMPORT numofcomport
GONIOMETER COMPORT        2
# GONIOMETER COOLSWITCH coolswitch
GONIOMETER COOLSWITCH        1
# GONIOMETER DACDEF hv ll wi ga pt deadtime hvm llm wim gam ptm deadtimem fin fimonref fiblt isfilter filter1 filter2 filter3
GONIOMETER DACDEF   646.2558     0.8000     1.7000   140.0000     0.0000 1.2500000E-005      0.0000     0.0000     0.0000     0.0000     0.0000 0.00000000000E+000   80000       0       0 TRUE    7.90000     1.00000     1.00000
# GONIOMETER KM4VIDEO numoffaces
GONIOMETER KM4VIDEO       40
# GONIOMETER MICROSCOPE type
GONIOMETER MICROSCOPE SIDEWAYS
# GONIOMETER MOTORBACKDIRECTION omdir thdir kadir phdir
GONIOMETER MOTORBACKDIRECTION 3 3 1 0
# GONIOMETER MOTORBACKSPEED ommusperstep thmusperstep kamusperstep phmusperstep
GONIOMETER MOTORBACKSPEED 300 300 300 300
# GONIOMETER MOTORBACKSTEPS steps
GONIOMETER MOTORBACKSTEPS      500
# GONIOMETER MOTORDETECTORCOMM omsteps thsteps kasteps phsteps
GONIOMETER MOTORDETECTORCOMM 30 36 61 34
# GONIOMETER MOTORDETECTORLIMIT steps
GONIOMETER MOTORDETECTORLIMIT     6400
# GONIOMETER MOTORDETECTORSPEED ommusperstep thmusperstep kamusperstep phmusperstep
GONIOMETER MOTORDETECTORSPEED 350 300 300 300
# GONIOMETER MOTORZEROZONE omdeg thdeg kadeg phdeg ax4 ax5 ax6 ax7 ax8 ax9 omdegpd thdegpd kadegpd phdegpd ax4pd ax5pd ax6pd ax7pd ax8pd ax9pd
GONIOMETER MOTORZEROZONE  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000
# GONIOMETER PHICORR0_170 c0 c10 c20 c30 c40 c50 c60 c70 c80 c90 c100 c110 c120 c130 c140 c150 c160 c170
GONIOMETER PHICORR0_170 0.00 10.00 20.00 30.00 40.00 50.00 60.00 70.00 80.00 90.05 100.00 110.00 120.00 130.00 140.00 150.00 160.00 170.00
# GONIOMETER PHICORR180_360 c180 c190 c200 c210 c220 c230 c240 c250 c260 c270 c280 c290 c300 c310 c320 c330 c340 c350 c360
GONIOMETER PHICORR180_360 180.00 190.00 200.00 210.00 220.00 230.00 240.00 250.00 260.00 270.00 280.00 290.00 300.00 310.00 320.00 330.00 340.00 350.00 360.00
# GONIOMETER SIMULATOR onoff
GONIOMETER SIMULATOR off
# GONIOMETER TYPE specifier bsorient. xtobsmm bsdiamm CCDType isfilter filter1 filter2 filter3 remeasuretype isfrontend isgen.check  ispdav. isareadetav. Ispd bsang bsx bsy bsh
GONIOMETER TYPE KUMA_KM4 TOP    15.00000    1.50000 BC2_SAPPHIRE   FALSE     1.00000    1.00000    1.00000   TIME FALSE FALSE TRUE TRUE FALSE     0.00000 0.0 0.0 1.50
# LOG ENABLE onoff
LOG ENABLE on
# MAR SIGMACONTROL sc sigmasc readnoise darknoise E1 E2 rejectsc
MAR SIGMACONTROL 1.0 4.0 1.0 0 1 1 1
# MAR TEST s1 s2 s3 s4 s5 d1 d2 d3 d4 d5 l1 l2 l3 l4 l5
MAR TEST NONE NONE NONE NONE NONE 0.0 0.0 0.0 0.0 0.0 0 0 0 0 0
# RESERVE TEST1 dscsv0 dsctTx dsctf dscwOa dscwOb dscwC dmobf lmosm lmoss dmosn1 dmosn2 dmosVmin iswsmode swcephi  swceom swcegiveup swceint swcehalfcut swsmiwidth
RESERVE TEST1   0.200000   0.000000   0.000000   1.000000   0.500000   1.001000   0.500000     0     0   3.000000  25.000000   0.050000     0 2.000 0.500   150 0.250     8 0.000
# RESERVE TEST2 dmabn11 dmabn12 dmabn13 dmabn21 dmabn22 dmabn23 dmabn31 dmabn32 dmabn33 lmass1 lmass2 lmass3
RESERVE TEST2   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000     0     1     2
# RESERVE TEST3 ddaDh ddaDv ddlv ldlN dtrTmin dtrTmax ltrNshell ltrNstop lHmin lHmax lKmin lKmax lLmin lLmax
RESERVE TEST3   0.000000   0.000000   0.000000 0   2.000000  40.000000 0 0   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000
# RESERVE TEST4 lrcn lrrn1 lrrn2 lrrn3 drrdO drrf
RESERVE TEST4 0 0 0 0   0.000000   5.000000
# RESERVE TEST5 darrh1 darrk1 darrl1 darrt1 darrfil1 darrh2 darrk2 darrl2 darrt2 darrfil2  darrh3 darrk3 darrl3 darrt3 darrfil3
RESERVE TEST5   0.000000   0.000000   0.000000   0.000000          0   0.000000   0.000000   0.000000   0.000000          0   0.000000   0.000000   0.000000   0.000000          0
# ROTATION ABSORPTIONCOEFFICIENTS wavelengthtype
ROTATION ABSORPTIONCOEFFICIENTS 0.000000 0.000000 0.000000
# ROTATION BEAM b2 b3 b2pd b3pd eqpd
ROTATION BEAM   0.000000    0.000000   0.00000   0.00000    0.00000
# ROTATION DETECTORORIENTATION d1 d2 d3 distanceinmm xorigininpix yorigininpix d1pd d2pd d3pd distanceinmmpd xorigininpixpd yorigininpixpd
ROTATION DETECTORORIENTATION    0.000000    0.000000    0.00000   {distance} {center_x} {center_y} -0.23494    0.11672    0.00000   50.65263  518.03346  502.88889
# ROTATION MONOCHROMATOR (dvalue|prepolfac) (orientation|type)
ROTATION MONOCHROMATOR    {polarization} SYNCHROTRON
# ROTATION PEAKINTEGRITYAREA enlargementfactor constantinpix userefMo userefCu refmod-closeMo,farMo,closeCu,farCu(d1,d2,t0,dd,x,y)
ROTATION PEAKINTEGRITYAREA 3.000 5.000 FALSE FALSE 0.745 0.401 0.766 60.000 505.164 493.255  0.745 0.401 0.766 100.000 505.164 493.255  0.745 0.401 0.766 60.000 505.164 493.255  0.745 0.401 0.766 100.000 505.164 493.255
# ROTATION SCANWIDTH c1_constant c2_thetadep ewaldconst ewaldlorentz
ROTATION SCANWIDTH {oscil} 0.000 1.000 0.000
# ROTATION ZEROCORRECTION omdeg thdeg kadeg phdeg ax4 ax5 ax6 ax7 ax8 ax9 omdegpd thdegpd kadegpd phdegpd ax4pd ax5pd ax6pd ax7pd ax8pd ax9pd
ROTATION ZEROCORRECTION  0.00000 0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000  0.00000
# ROTSIMULATOR ALPHAANGLEINDEG alpha
ROTSIMULATOR ALPHAANGLEINDEG       {alpha}
# ROTSIMULATOR BACKGROUNDNOISE typeofnoise
ROTSIMULATOR BACKGROUNDNOISE NONE
# ROTSIMULATOR BEAM b2 b3
ROTSIMULATOR BEAM 0.0 0.0
# ROTSIMULATOR BETAANGLEINDEG beta
ROTSIMULATOR BETAANGLEINDEG        {beta}
# ROTSIMULATOR CRYSTALLATTICE a b c alpha beta gamma
ROTSIMULATOR CRYSTALLATTICE 10.0 10.0 10.0 90.0 90.0 90.0
# ROTSIMULATOR CRYSTALORIENTATION r1 r2 r3
ROTSIMULATOR CRYSTALORIENTATION 0.0 0.0 0.0
# ROTSIMULATOR DETECTORORIENTATION d1 d2 d3 distanceinmm xorigininpix yorigininpix
ROTSIMULATOR DETECTORORIENTATION 0.0 0.0 0.0 {distance} {center_x} {center_y}
# ROTSIMULATOR DIFFRACTIONINTENSITY int dw
ROTSIMULATOR DIFFRACTIONINTENSITY 10000 0.0
# ROTSIMULATOR PEAKSHAPE xsizeinmm xsigmafactor ysizeinmm ysigmafactor
ROTSIMULATOR PEAKSHAPE 1.5 15.0 1.5 15.0
# ROTSIMULATOR SCANWIDTH c1_constant c2_thetadep omsigmafactor
ROTSIMULATOR SCANWIDTH 1.0 1.0 10.0
# ROTSIMULATOR SIMULATOR onoff
ROTSIMULATOR SIMULATOR off
# ROTSIMULATOR ZEROCORRECTION om th ka ph
ROTSIMULATOR ZEROCORRECTION 0.0 0.0 0.0 0.0
# SHAPE ABSORPTIONCOEFFICIENT coeffininvmm z
SHAPE ABSORPTIONCOEFFICIENT   0.000000   1.000000
# SHAPE CHEMICALFORMULA formula
SHAPE CHEMICALFORMULA "C10 H10 O5 N5"
# SHAPE FACE h k l dinmm
SHAPE FACE 0.0 0.0 0.0 0.0
# SHAPE VIDEOGRABBER grabber_type vfw_channel
SHAPE VIDEOGRABBER VFW 0
# SHAPE VIDEOSETUP cenx ceny scale
SHAPE VIDEOSETUP 384 288 0.00101890
# STOE TEST s1 s2 s3 s4 s5 d1 d2 d3 d4 d5 l1 l2 l3 l4 l5
STOE TEST NONE NONE NONE NONE NONE 0.0 0.0 0.0 0.0 0.0 0 0 0 0 0
#END SETUP XCALIBUR SYSTEM
"""


class CCD_FILEVERSION(Enum):
    VERS_1_1 = 0x00010001
    VERS_1_2 = 0x00010002
    VERS_1_3 = 0x00010003
    VERS_1_4 = 0x00010004
    VERS_1_5 = 0x00010005
    VERS_1_6 = 0x00010006
    VERS_1_7 = 0x00010007
    VERS_1_8 = 0x00010008
    VERS_1_9 = 0x00010009
    VERS_1_10 = 0x0001000a
    VERS_1_11 = 0x0001000b
    VERS_1_12 = 0x0001000c


CCD_FILEVERSION_VERS_HIGHEST = CCD_FILEVERSION.VERS_1_12


class CHIPCHARACTERISTICS_TREATMENT(Enum):
    IGNORE = 0
    REPLACE = 1
    AVERAGE = 2
    REPLACELEFT = 3
    REPLACERIGHT = 4
    AVERAGELEFTRIGHT = 5
    TREATMENT_REPLACETOP = 6
    REPLACEBOTTOM = 7
    AVERAGETOPBOTTOM = 8


class CHIPCHARACTERISTICS_POLYGONTYPE(Enum):
    TRIANGLE = 0
    RECTANGLE = 1
    POLYGON = 2
    MAXPOINTS = 6


class CHIPCHARACTERISTICS_SCINTILLATORID(Enum):
    NOTPRESENT = 0
    GREEN400 = 1
    LEXEL40 = 2
    LEXEL60 = 3
    LEXEL100 = 4
    RAREXMED = 5
    RAREXFINE = 6
    GREEN400_NEW = 7


CHIPCHARACTERISTICS_SCINTILLATORID_FIRST = CHIPCHARACTERISTICS_SCINTILLATORID.GREEN400
CHIPCHARACTERISTICS_SCINTILLATORID_LAST = CHIPCHARACTERISTICS_SCINTILLATORID.GREEN400_NEW


class SCAN_TYPE(Enum):
        Omega = 0
        Phi = 4


@dataclass
class ChipPoint:
    ix: int = 0
    iy: int = 0
    SIZE = 4

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        return cls(*struct.unpack("<HH", buffer[:cls.SIZE]))

    def dumps(self):
        return struct.pack("<HH", self.ix, self.iy)


@dataclass
class ChipBadPoint:
    spt: ChipPoint
    sptreplace1:ChipPoint
    sptreplace2: ChipPoint
    itreatment:int = 0
    SIZE = 14

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        return cls(ChipPoint.loads(buffer[:4]),
                   ChipPoint.loads(buffer[4:8]),
                   ChipPoint.loads(buffer[8:12]),
                   struct.unpack("<H", buffer[12:cls.SIZE]))

    def dumps(self):
        return  self.spt.dumps() + \
                self.sptreplace1.dumps() + \
                self.sptreplace2.dumps() + \
                struct.pack("<H", self.itreatment)


@dataclass
class ChipBadRow:
    """ROW STARTX ENDX Y"""
    sptstart: ChipPoint
    sptend: ChipPoint
    sptstartreplace:ChipPoint
    sptendreplace: ChipPoint
    itreatment:int
    SIZE = 18

    @classmethod
    def loads(cls, buffer):
        cls.SIZE = cls.SIZE
        assert len(buffer) >= cls.SIZE
        return cls(ChipPoint.loads(buffer[:4]),
                   ChipPoint.loads(buffer[4:8]),
                   ChipPoint.loads(buffer[8:12]),
                   ChipPoint.loads(buffer[12:16]),
                   struct.unpack("<H", buffer[16:cls.SIZE])[0])

    def dumps(self):
        return  self.sptstart.dumps() + \
                self.sptend.dumps() + \
                self.sptstartreplace.dumps() + \
                self.sptendreplace.dumps() + \
                struct.pack("<H", self.itreatment)


@dataclass
class ChipBadColumn:
    """COLUMN X STARTY ENDY"""
    sptstart: ChipPoint
    sptend: ChipPoint
    sptstartreplace:ChipPoint
    sptendreplace: ChipPoint
    itreatment:int
    ilowlimit: int
    ihighlimit: int
    SIZE = 22

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        return cls(ChipPoint.loads(buffer[:4]),
                   ChipPoint.loads(buffer[4:8]),
                   ChipPoint.loads(buffer[8:12]),
                   ChipPoint.loads(buffer[12:16]),
                   *struct.unpack("<HHH", buffer[16:cls.SIZE]))

    def dumps(self):
        return self.sptstart.dumps() + \
               self.sptend.dumps() + \
               self.sptstartreplace.dumps() + \
               self.sptendreplace.dumps() + \
               struct.pack("<HHH", self.itreatment, self.ilowlimit, self.ihighlimit)


@dataclass
class ChipBadPolygon:
    itype:int
    ipoints: int
    iax: list
    iay: list
    SIZE = 2 * (2 + 2 * CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value)

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        lst = struct.unpack("<HH" + "H"*2 * CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value,
                            buffer[:cls.SIZE])
        return cls(lst[0], lst[1],
                   lst[2:2 + CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value],
                   lst[2 + CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value:],)

    def dumps(self):
        for lst in (self.iax, self.iay):
            if len(lst) < CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value:
                lst += [0] * (CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value - len(lst))
        return struct.pack("<HH" + "H"*2 * CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value,
                           self.itype, self.ipoints,
                           *self.iax[:CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value],
                           *self.iay[:CHIPCHARACTERISTICS_POLYGONTYPE.MAXPOINTS.value])


@dataclass
class ChipMachineFunction:
    """
    xl is log10 of true Xray intensity
    y=A * exp (B * xl)
    """
    iismachinefunction: int = 0
    da_machinefct: float = 0.0
    db_machinefct: float = 0.0
    SIZE = 18

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        lst = struct.unpack("<Hdd",
                            buffer[:cls.SIZE])
        return cls(lst[0], lst[1], lst[2])

    def dumps(self):
        return struct.pack("<Hdd", self.iismachinefunction, self.da_machinefct, self.db_machinefct)


@dataclass
class Sweep:
    "Describes one scan or sweep, continuous rotation of one motor"
    iscanindex: int = 0
    iscantype: int = 0
    dwunknown1: int = 0
    domega: float = 0.0
    dtheta: float = 0.0
    dkappa: float = 0.0
    dphi: float = 0.0
    dstart: float = 0.0
    dend: float = 0.0
    dwidth: float = 0.0
    dunknown2: float = 0.0
    iunknown3: int = 0.0;
    iunknown4: int = 0.0;
    iunknown5: int = 0.0;
    iunknown6: int = 0.0;
    dexposure: float = 0.0
    SIZE = 88

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        lst = struct.unpack("<HHIddddddddHHHHd",
                            buffer[:cls.SIZE])
        return cls(*lst)

    def dumps(self):
        return struct.pack("<HHIddddddddHHHHd", self.iscanindex, self.iscantype,
                           self.dwunknown1, self.domega, self.dtheta, self.dkappa,
                           self.dphi, self.dstart, self.dend, self.dwidth,
                           self.dunknown2, self.iunknown3, self.iunknown4, self.iunknown5, self.iunknown6,
                           self.dexposure)


@dataclass
class RunDescription:
    """Names are using the hugarian notation: the first letter describes the type
    
    strings are 256bytes long
    floats are 64bits
    integers are 16bits, probably unsigned
    """
    cprefix: str = "n/a"
    cfolder: str = "n/a"
    inumofsweeps: int = 0
    dunknown: float = 0.0
    pssweep:list = tuple()
    SIZE = 528

    @classmethod
    def read(cls, filename):
        """Read the filename.run"""
        with open(filename, "rb") as f:
            bytestream = f.read()
        return cls.loads(bytestream)

    @classmethod
    def loads(cls, buffer):
        assert len(buffer) >= cls.SIZE
        prefix = buffer[:256].decode().strip("\x00")
        path = buffer[256:512].decode().strip("\x00")
        lst = struct.unpack("<Qd", buffer[512:cls.SIZE])
        self = cls(prefix, path, *lst, [])
        size = cls.SIZE
        for _ in range(self.inumofsweeps):
            self.pssweep.append(Sweep.loads(buffer[size:size + Sweep.SIZE]))
            size += Sweep.SIZE
        return self

    def save(self, filename):
        with open(filename, "wb")  as w:
            w.write(self.dumps())

    def dumps(self):

        # Some helper functions
        def record_str(key):
            value = self.__getattribute__(key)
            buffer[end:end + len(value)] = value.encode()
            return 256

        def record_struct(key, dtype):
            value = self.__getattribute__(key)
            size = struct.calcsize(dtype)
            if isinstance(value, (list, tuple)):
                buffer[end:end + size] = struct.pack(dtype, *value)
            else:
                buffer[end:end + size] = struct.pack(dtype, value)
            return size

        self.inumofsweeps = len(self.pssweep)

        size = self.SIZE + self.inumofsweeps * Sweep.SIZE
        buffer = bytearray(size)
        end = 0
        end += record_str("cprefix")
        end += record_str("cfolder")
        end += record_struct("inumofsweeps", "<Q")
        end += record_struct("dunknown", "<d")
        for sweep in self.pssweep:
            binary = sweep.dumps()
            length = len(binary)
            start = end
            end += length
            buffer[start:end] = binary
        return bytes(buffer[:end])


@dataclass
class CcdCharacteristiscs:
    """Names are using the hugarian notation: the first letter describes the type
    
    strings are 256bytes long
    floats are 64bits
    integers are 16bits, probably unsigned
    """
    dwversion: int = 0
    ddarkcurrentinADUpersec:float = 0.0
    dreadnoiseinADU: float = 0.0
    ccharacteristicsfil: str = "n/a"
    cccdproducer: str = "n/a"
    cccdchiptype: str = "n/a"
    cccdchipserial: str = "n/a"
    ctaperproducer: str = "n/a"
    ctapertype: str = "n/a"
    ctaperserial: str = "n/a"

    iisfip60origin: int = 0
    ifip60xorigin: int = 0
    ifip60yorigin: int = 0

    inumofcornermasks: int = 0
    iacornermaskx: list = tuple()
    iacornermasky:list = tuple()
    inumofglowingcornermasks: int = 0
    iaglowingcornermaskx: list = tuple()
    iaglowingcornermasky:list = tuple()

    ibadpolygons: int = 0
    pschipbadpolygon:list = tuple()

    ibadpoints: int = 0
    pschipbadpoint: list = tuple()

    ibadcolumns: int = 0
    pschipbadcolumn: list = tuple()
    ibadcolumns1x1: int = 0
    pschipbadcolumn1x1: list = tuple()
    ibadcolumns2x2: int = 0
    pschipbadcolumn2x2: list = tuple()
    ibadcolumns4x4: int = 0
    pschipbadcolumn4x4: list = tuple()

    ibadrows: int = 0
    pschipbadrow: list = tuple()

    iscintillatorid: int = 0
    # iisscintillatorid: int =0
    dgain_cu:float = 0.0
    dgain_mo:float = 0.0
    chipmachinefunction: ChipMachineFunction = None

    ibadrows1x1: int = 0
    pschipbadrow1x1: list = tuple()
    ibadrows2x2: int = 0
    pschipbadrow2x2: list = tuple()
    ibadrows4x4: int = 0
    pschipbadrow4x4: list = tuple()

    def _clip_string(self):
        """Clip all strings to 256 chars"""
        for key in ("ccharacteristicsfil", "cccdproducer", "cccdchiptype", "cccdchipserial", "ctaperproducer", "ctapertype", "ctaperserial"):
            value = self.__getattribute__(key)
            l = len(value)
            if l > 256:
                self.__setattr__(key, value[:256])

    @classmethod
    def read(cls, filename):
        """The the filename.ccd"""
        with open(filename, "rb") as f:
            bytestream = f.read()
        return cls.loads(bytestream)

    @classmethod
    def loads(cls, bytestream):
        ended = False
        length = len(bytestream)
        dico = {}
        if length > 1854:
            dico["dwversion"] = struct.unpack("I", bytestream[:4])[0]  # VERSION
            dico["ddarkcurrentinADUpersec"] = struct.unpack("d", bytestream[4:12])[0]  # DARK CURRENT IN ADU
            dico["dreadnoiseinADU"] = struct.unpack("d", bytestream[12:20])[0]  # READ NOSE IN ADU
            dico["ccharacteristicsfil"] = bytestream[20:276].decode().strip("\x00")  # CHARACTISTICS FILE NAME
            dico["cccdproducer"] = bytestream[276:532].decode().strip("\x00")  # PRODUCER
            dico["cccdchiptype"] = bytestream[532:788].decode().strip("\x00")  # CHIP TYPE
            dico["cccdchipserial"] = bytestream[788:1044].decode().strip("\x00")  # CHIP SERIAL
            dico["ctaperproducer"] = bytestream[1044:1300].decode().strip("\x00")  # TAPER PRODUCER
            dico["ctapertype"] = bytestream[1300:1556].decode().strip("\x00")  #  TAPER TYPE
            dico["ctaperserial"] = bytestream[1556:1812].decode().strip("\x00")  #  TAPER SERIAL
            dico["iisfip60origin"], dico["ifip60xorigin"], dico["ifip60yorigin"] = struct.unpack("HHH", bytestream[1812:1818])  # FIP60ORIGIN

            dico["inumofcornermasks"] = struct.unpack("H", bytestream[1818:1820])  # CORNER MASKS
            dico["iacornermaskx"] = struct.unpack("HHHH", bytestream[1820:1828])
            dico["iacornermasky"] = struct.unpack("HHHH", bytestream[1828:1836])

            dico["inumofglowingcornermasks"] = struct.unpack("H", bytestream[1836:1838])  # GLOWINGCORNER MASKS
            dico["iaglowingcornermaskx"] = struct.unpack("HHHH", bytestream[1838:1846])
            dico["iaglowingcornermasky"] = struct.unpack("HHHH", bytestream[1846:1854])
        else:
            ended = True
        # NUMBER OF BAD POLYGONS
        start = 1856
        if not ended and start <= length:
            try:
                dico["ibadpolygons"] = struct.unpack("H", bytestream[1854:1856])[0]
                polygons = dico["pschipbadpolygon"] = []
                for _ in range(dico["ibadpolygons"]):
                    polygons.append(ChipBadPolygon.loads(bytestream[start:]))
                    start += ChipBadPolygon.SIZE
            except AssertionError:
                ended = True
        # NUMBER OF BAD POINTS
        if not ended and start + 2 <= length:
            try:
                dico["ibadpoints"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                points = dico["pschipbadpoint"] = []
                for _ in range(dico["ibadpoints"]):  # LOOP OVER BAD POINTS
                    points.append(ChipBadPoint.loads(bytestream[start:]))
                    start += ChipBadPoint.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD COLS
        if not ended and start + 2 <= length:
            try:
                dico["ibadcolumns"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadcolumn"] = []
                for _ in range(dico["ibadcolumns"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadColumn.loads(bytestream[start:]))
                    start += ChipBadColumn.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD COLS 1X1
        if not ended and start + 2 <= length:
            try:
                dico["ibadcolumns1x1"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadcolumn1x1"] = []
                for _ in range(dico["ibadcolumns1x1"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadColumn.loads(bytestream[start:]))
                    start += ChipBadColumn.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD COLS 2X2
        if not ended and start + 2 <= length:
            try:
                dico["ibadcolumns2x2"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadcolumn2x2"] = []
                for _ in range(dico["ibadcolumns2x2"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadColumn.loads(bytestream[start:]))
                    start += ChipBadColumn.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD COLS 4X4
        if not ended and start + 2 <= length:
            try:
                dico["ibadcolumns4x4"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadcolumn4x4"] = []
                for _ in range(dico["ibadcolumns4x4"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadColumn.loads(bytestream[start:]))
                    start += ChipBadColumn.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD ROWS
        if not ended and start + 2 <= length:
            try:
                dico["ibadrows"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadrow"] = []
                for _ in range(dico["ibadrows"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadRow.loads(bytestream[start:]))
                    start += ChipBadRow.SIZE
            except AssertionError:
                ended = True

        if not ended and start + 18 <= length:
            try:
                dico["iscintillatorid"] = struct.unpack("H", bytestream[start:start + 2])[0]  # SCINTILLATOR DESCRIPTION
                dico["dgain_mo"] = struct.unpack("d", bytestream[start + 2:start + 10])[0]  # GAINMO
                dico["dgain_cu"] = struct.unpack("d", bytestream[start + 10:start + 18])[0]  # GAINCU
                start += 18
                dico["chipmachinefunction"] = ChipMachineFunction.loads(bytestream[start:])  # IISMACHINEFUNCTION
                start += ChipMachineFunction.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD ROWS 1X1
        if not ended and start + 2 <= length:
            try:
                dico["ibadrows1x1"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadrow1x1"] = []
                for _ in range(dico["ibadcolumns1x1"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadRow.loads(bytestream[start:]))
                    start += ChipBadRow.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD ROWS 2X2
        if not ended and start + 2 <= length:
            try:
                dico["ibadrows2x2"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadrow2x2"] = []
                for _ in range(dico["ibadrows2x2"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadRow.loads(bytestream[start:]))
                    start += ChipBadRow.SIZE
            except AssertionError:
                ended = True

        # NUMBER OF BAD ROWS 4X4
        if not ended and start + 2 <= length:
            try:
                dico["ibadrows4x4"] = struct.unpack("H", bytestream[start:start + 2])[0]
                start += 2
                columns = dico["pschipbadrow4x4"] = []
                for _ in range(dico["ibadrows4x4"]):  # LOOP OVER BAD COLS
                    columns.append(ChipBadRow.loads(bytestream[start:]))
                    start += ChipBadRow.SIZE
            except AssertionError:
                ended = True

        self = cls(**dico)
        return self

    def save(self, filename):
        with open(filename, "wb")  as w:
            w.write(self.dumps())

    def dumps(self):
        """Dump the content of the struct as a bytestream."""
        buffer = bytearray(4096)
        end = 0
        # prepare the structure
        if not self.dwversion:
            self.dwversion = CCD_FILEVERSION_VERS_HIGHEST
        self._clip_string()
        self.ibadpolygons = len(self.pschipbadpolygon)
        self.ibadpoints = len(self.pschipbadpoint)
        for empty_4_tuple in ("iacornermaskx", "iacornermasky",
                              "iaglowingcornermaskx", "iaglowingcornermasky"):
            value = self.__getattribute__(empty_4_tuple)
            if len(value) == 0:
                self.__setattr__(empty_4_tuple, [0, 0, 0, 0])
        if self.chipmachinefunction is None:
            self.chipmachinefunction = ChipMachineFunction()

        # Some helper functions
        def record_str(key):
            value = self.__getattribute__(key)
            buffer[end:end + len(value)] = value.encode()
            return 256

        def record_struct(key, dtype):
            value = self.__getattribute__(key)
            size = struct.calcsize(dtype)
            if isinstance(value, (list, tuple)):
                buffer[end:end + size] = struct.pack(dtype, *value)
            else:
                buffer[end:end + size] = struct.pack(dtype, value)
            return size

        def record_object(key):
            value = self.__getattribute__(key)
            tmp = value.dumps()
            ltmp = len(tmp)
            buffer[end:end + ltmp] = tmp
            return ltmp

        def record_variable(key, subkey, dtype="H"):
            size = struct.calcsize(dtype)
            values = self.__getattribute__(subkey)
            nitems = len(values)
            self.__setattr__(key, nitems)
            buffer[end:end + size] = struct.pack(dtype, nitems)
            for i in values:
                tmp = i.dumps()
                ltmp = len(tmp)
                buffer[end + size:end + size + ltmp] = tmp
                size += ltmp
            return size

        end += record_struct("dwversion", "I")  # VERSION
        end += record_struct("ddarkcurrentinADUpersec", "d")  # DARK CURRENT IN ADU
        end += record_struct("dreadnoiseinADU", "d")  # READ NOSE IN ADU
        end += record_str("ccharacteristicsfil")
        end += record_str("cccdproducer")
        end += record_str("cccdchiptype")
        end += record_str("cccdchipserial")
        end += record_str("ctaperproducer")
        end += record_str("ctapertype")
        end += record_str("ctaperserial")
        end += record_struct("iisfip60origin", "H")
        end += record_struct("ifip60xorigin", "H")
        end += record_struct("ifip60yorigin", "H")
        end += record_struct("inumofcornermasks", "H")
        end += record_struct("iacornermaskx", "HHHH")
        end += record_struct("iacornermasky", "HHHH")
        end += record_struct("inumofglowingcornermasks", "H")
        end += record_struct("iaglowingcornermaskx", "HHHH")
        end += record_struct("iaglowingcornermasky", "HHHH")
        end += record_variable("ibadpolygons", "pschipbadpolygon")
        end += record_variable("ibadpoints", "pschipbadpoint")
        end += record_variable("ibadcolumns", "pschipbadcolumn")
        end += record_variable("ibadcolumns1x1", "pschipbadcolumn1x1")
        end += record_variable("ibadcolumns2x2", "pschipbadcolumn2x2")
        end += record_variable("ibadcolumns4x4", "pschipbadcolumn4x4")
        end += record_variable("ibadrows", "pschipbadrow")
        end += record_struct("iscintillatorid", "H")
        end += record_struct("dgain_mo", "d")
        end += record_struct("dgain_cu", "d")
        end += record_object("chipmachinefunction")
        end += record_variable("ibadrows1x1", "pschipbadrow1x1")
        end += record_variable("ibadrows2x2", "pschipbadrow2x2")
        end += record_variable("ibadrows4x4", "pschipbadrow4x4")
        return bytes(buffer[:end])

    def build_mask(self, shape):
        """Rebuild a mask from all pixels/rectangles masked"""
        mask = numpy.zeros(shape, dtype=numpy.int8)
        for poly in self.pschipbadpolygon:
            if poly.itype == CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value:
                mask[poly.iay[0]:1 + poly.iay[1], poly.iax[0]:1 + poly.iax[1]] = 1
        for pt in self.pschipbadpoint:
            mask[pt.spt.iy, pt.spt.ix] = 1
        return mask


class XcaliburImage(FabioImage):
    """FabIO image class for CrysalisPro mask image
    """

    DESCRIPTION = "Xcalibur binary struct of masked pixels"

    DEFAULT_EXTENSIONS = []

    def __init__(self, *arg, **kwargs):
        """
        """
        FabioImage.__init__(self, *arg, **kwargs)

    def _readheader(self, infile):
        """
        Read and decode the header of an image:

        :param infile: Opened python file (can be stringIO or bzipped file)
        """
        # list of header key to keep the order (when writing)
        self.header = self.check_header()

    def read(self, fname, frame=None):
        """
        Try to read image

        :param fname: name of the file
        :param frame: number of the frame
        """

        self.resetvals()
        with self._open(fname) as infile:
            self._readheader(infile)
            # read the image data and declare it

        shape = (50, 60)
        self.data = numpy.zeros(shape, dtype=self.uint16)
        # Nota: dim1, dim2, bytecode and bpp are properties defined by the dataset
        return self

    def decompose(self, full=False):
        """Decompose a mask defined as a 2D binary image in 
        
        * vertical+horizontal gaps
        * rectangles
        * bad pixels 
        
        :param: full: deal only with gaps (False) or perform the complete analysis (True) 
        :return: CcdCharacteristiscs struct.
        """
        ccd = CcdCharacteristiscs(CCD_FILEVERSION_VERS_HIGHEST.value,
                                  pschipbadpolygon=[],
                                  pschipbadpoint=[])
        mask = numpy.array(self.data, dtype=bool)
        shape = mask.shape

        row_gaps = self._search_gap(mask, dim=1)
        col_gaps = self._search_gap(mask, dim=0)

        ccd.ibadpolygons = len(row_gaps) + len(col_gaps)
        ccd.pschipbadpolygon = []
        for gap in row_gaps:
            polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4,
                                     [0, shape[1] - 1], [gap[0], gap[1] - 1])
            ccd.pschipbadpolygon.append(polygon)
        for gap in col_gaps:
            polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4,
                                     [gap[0], gap[1] - 1], [0, shape[0] - 1])
            ccd.pschipbadpolygon.append(polygon)

        try:
            import pyFAI.ext.dynamic_rectangle
        except ImportError:
            logger.warning("PyFAI not available: only a coarse description of the mask is provided")
            full = False
        if not full:
            return ccd
        # Decompose detector into a set of modules, then extract patches of mask for each of them:
        c = 0
        for cg in col_gaps + [(self.shape[1], self.shape[1])]:
            r = 0
            for rg in row_gaps + [(self.shape[0], self.shape[0])]:
                mm = mask[r:rg[0], c:cg[0]]
                if mm.size:
                    rectangles = pyFAI.ext.dynamic_rectangle.decompose_mask(mm)
                    for rec in rectangles:
                        if rec.area == 1:
                            point = ChipPoint(c + rec.col, r + rec.row)
                            bad_point = ChipBadPoint(point, point, point, CHIPCHARACTERISTICS_TREATMENT.IGNORE.value)
                            ccd.ibadpoints += 1
                            ccd.pschipbadpoint.append(bad_point)
                        else:
                            ccd.ibadpolygons += 1
                            polygon = ChipBadPolygon(CHIPCHARACTERISTICS_POLYGONTYPE.RECTANGLE.value, 4,
                                                     [c + rec.col, c + rec.col + rec.width - 1],
                                                     [r + rec.row, r + rec.row + rec.height - 1])
                            ccd.pschipbadpolygon.append(polygon)
                r = rg[1]
            c = cg[1]
        return ccd

    @staticmethod
    def _search_gap(mask, dim=0):

        shape = mask.shape
        m0 = numpy.sum(mask, axis=dim, dtype="int") == shape[dim]
        if m0.any():
            m0 = numpy.asarray(m0, "int8")
            d0 = m0[1:] - m0[:-1]
            starts = numpy.where(d0 == 1)[0]
            stops = numpy.where(d0 == -1)[0]
            if  (len(starts) == 0):
                starts = numpy.array([-1])
            if  (len(stops) == 0):
                stops = numpy.array([len(m0) - 1])
            if (stops[0] < starts[0]):
                starts = numpy.concatenate(([-1], starts))
            if (stops[-1] < starts[-1]):
                stops = numpy.concatenate((stops, [len(m0) - 1]))
            r0 = [ (start + 1, stop + 1) for start, stop  in zip(starts, stops)]
        else:
            r0 = []
        return r0

    def save_par(self, path, prefix, **kwargs):
        """Generate a *.par" file which contains the parameters of the scan 
        """

        dico = {"alpha": kwargs.get("alpha", 50),
                "beta": kwargs.get("beta", 0),
                "wavelength": kwargs.get("wavelength", 1),
                "polarization": kwargs.get("polarization", 0.98),
                "shape_x": self.shape[1],
                "shape_y": self.shape[0],
                "distance": kwargs.get("distance", 100),  # mm
                "path": path,
                "ccd_file": prefix + ".ccd",
                "oscil": kwargs.get("oscil", 1),
                "center_x":kwargs.get("center_x", self.shape[1] / 2),
                "center_y":kwargs.get("center_y", self.shape[1] / 2),
                "date": time.ctime()
                }
        string = PAR_TEMPLATE.format(**dico)
        blines = [b"\xa7" + i[1:].rstrip().encode() if i.startswith("#") else i.rstrip().encode() for i in string.split("\n")]
        with open(os.path.join(path, prefix + ".par"), "wb") as w:
            w.write(b"\r\n".join(blines))


# This is for compatibility with old code:
xcaliburimage = XcaliburImage
