import numpy as np

from autoarray.structures.arrays import abstract_array
from autoarray.structures.frames import abstract_frame
from autoarray.mask import mask_2d as msk
from autoarray.util import array_util, geometry_util, frame_util


class Frame2D(abstract_frame.AbstractFrame2D):
    def __new__(
        cls, array, mask, original_roe_corner=(1, 0), scans=None, exposure_info=None
    ):
        """Abstract class for the geometry of a CTI Image.

        A f.FrameArray is stored as a 2D ndarrays. When this immage is passed to arctic, clocking goes towards
        the 'top' of the ndarrays (e.g. towards row 0). Trails therefore appear towards the 'bottom' of the arrays
        (e.g. the final row).

        Arctic has no in-built functionality for changing the direction of clocking depending on the input
        configuration file. Therefore, image rotations are handled before arctic is called, using the functions
        defined in this class (and its children). These routines define how an image is rotated before parallel
        and serial clocking and how to reorient the image back to its original orientation after clocking is performed.

        Currently, only four geometries are available, which are specific to Euclid (and documented in the
        *QuadGeometryEuclid* class).

        Parameters
        -----------
        parallel_overscan : frame.Region2D
            The parallel overscan region of the ci_frame.
        serial_prescan : frame.Region2D
            The serial prescan region of the ci_frame.
        serial_overscan : frame.Region2D
            The serial overscan region of the ci_frame.
        """

        if type(array) is list:
            array = np.asarray(array)

        array[mask == True] = 0.0

        obj = array.view(cls)
        obj.mask = mask
        obj.store_slim = False
        obj.zoom_for_plot = False
        obj.original_roe_corner = original_roe_corner
        obj.scans = scans or abstract_frame.Scans()
        obj.exposure_info = exposure_info

        return obj

    @classmethod
    def manual(
        cls, array, pixel_scales, roe_corner=(1, 0), scans=None, exposure_info=None
    ):
        """Abstract class for the geometry of a CTI Image.

        A FrameArray is stored as a 2D ndarrays. When this immage is passed to arctic, clocking goes towards
        the 'top' of the ndarrays (e.g. towards row 0). Trails therefore appear towards the 'bottom' of the arrays
        (e.g. the final row).

        Arctic has no in-built functionality for changing the direction of clocking depending on the input
        configuration file. Therefore, image rotations are handled before arctic is called, using the functions
        defined in this class (and its children). These routines define how an image is rotated before parallel
        and serial clocking and how to reorient the image back to its original orientation after clocking is performed.

        Currently, only four geometries are available, which are specific to Euclid (and documented in the
        *QuadGeometryEuclid* class).

        Parameters
        -----------
        parallel_overscan : frame.Region2D
            The parallel overscan region of the ci_frame.
        serial_prescan : frame.Region2D
            The serial prescan region of the ci_frame.
        serial_overscan : frame.Region2D
            The serial overscan region of the ci_frame.
        """

        array = abstract_array.convert_array(array=array)

        pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales)

        mask = msk.Mask2D.unmasked(shape_native=array.shape, pixel_scales=pixel_scales)

        scans = abstract_frame.Scans.rotated_from_roe_corner(
            roe_corner=roe_corner, shape_native=array.shape, scans=scans
        )

        return cls(
            array=frame_util.rotate_array_from_roe_corner(
                array=array, roe_corner=roe_corner
            ),
            mask=mask,
            original_roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
        )

    @classmethod
    def manual_mask(
        cls, array, mask, scans=None, roe_corner=(1, 0), exposure_info=None
    ):

        """Abstract class for the geometry of a CTI Image.

        A FrameArray is stored as a 2D ndarrays. When this immage is passed to arctic, clocking goes towards
        the 'top' of the ndarrays (e.g. towards row 0). Trails therefore appear towards the 'bottom' of the arrays
        (e.g. the final row).

        Arctic has no in-built functionality for changing the direction of clocking depending on the input
        configuration file. Therefore, image rotations are handled before arctic is called, using the functions
        defined in this class (and its children). These routines define how an image is rotated before parallel
        and serial clocking and how to reorient the image back to its original orientation after clocking is performed.

        Currently, only four geometries are available, which are specific to Euclid (and documented in the
        *QuadGeometryEuclid* class).

        Parameters
        -----------
        parallel_overscan : Region2D
            The parallel overscan region of the ci_frame.
        serial_prescan : Region2D
            The serial prescan region of the ci_frame.
        serial_overscan : Region2D
            The serial overscan region of the ci_frame.
        """

        array = abstract_array.convert_array(array=array)

        array = frame_util.rotate_array_from_roe_corner(
            array=array, roe_corner=roe_corner
        )
        mask = frame_util.rotate_array_from_roe_corner(
            array=mask, roe_corner=roe_corner
        )

        array[mask == True] = 0.0

        scans = abstract_frame.Scans.rotated_from_roe_corner(
            roe_corner=roe_corner, shape_native=array.shape, scans=scans
        )

        return cls(
            array=array,
            mask=mask,
            original_roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
        )

    @classmethod
    def full(
        cls,
        fill_value,
        shape_native,
        pixel_scales,
        roe_corner=(1, 0),
        scans=None,
        exposure_info=None,
    ):

        return cls.manual(
            array=np.full(fill_value=fill_value, shape=shape_native),
            pixel_scales=pixel_scales,
            roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
        )

    @classmethod
    def ones(
        cls,
        shape_native,
        pixel_scales,
        roe_corner=(1, 0),
        scans=None,
        exposure_info=None,
    ):
        return cls.full(
            fill_value=1.0,
            shape_native=shape_native,
            roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
            pixel_scales=pixel_scales,
        )

    @classmethod
    def zeros(
        cls,
        shape_native,
        pixel_scales,
        roe_corner=(1, 0),
        scans=None,
        exposure_info=None,
    ):
        return cls.full(
            fill_value=0.0,
            shape_native=shape_native,
            pixel_scales=pixel_scales,
            roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
        )

    @classmethod
    def extracted_frame_from_frame_and_extraction_region(cls, frame, extraction_region):

        scans = abstract_frame.Scans.after_extraction(
            frame=frame, extraction_region=extraction_region
        )

        return cls.manual(
            array=frame[extraction_region.slice],
            roe_corner=frame.original_roe_corner,
            scans=scans,
            exposure_info=frame.exposure_info,
            pixel_scales=frame.pixel_scales,
        )

    @classmethod
    def from_fits(
        cls,
        file_path,
        hdu,
        pixel_scales,
        roe_corner=(1, 0),
        scans=None,
        exposure_info=None,
    ):
        """Load the image ci_data from a fits file.

        Params
        ----------
        path : str
            The path to the ci_data
        filename : str
            The file name of the fits image ci_data.
        hdu : int
            The HDU number in the fits file containing the image ci_data.
        frame_geometry : FrameArray.FrameGeometry
            The geometry of the ci_frame, defining the direction of parallel and serial clocking and the \
            locations of different scans of the CCD (overscans, prescan, etc.)
        """

        pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales)

        array = array_util.numpy_array_2d_from_fits(file_path=file_path, hdu=hdu)

        return cls.manual(
            array=array,
            pixel_scales=pixel_scales,
            roe_corner=roe_corner,
            scans=scans,
            exposure_info=exposure_info,
        )

    @classmethod
    def from_frame(cls, frame, mask):
        return Frame2D(
            array=frame,
            mask=mask,
            original_roe_corner=frame.original_roe_corner,
            scans=abstract_frame.Scans.from_frame(frame=frame),
            exposure_info=frame.exposure_info,
        )
