import numpy as np
from astropy.io import fits

from hcipy import Field, Wavefront, DeformableMirror, FraunhoferPropagator
from hcipy import SurfaceApodizer, SurfaceAberrationAtDistance
from hcipy import make_pupil_grid, make_circular_aperture, make_focal_grid
from hcipy import make_xinetics_influence_functions
from hcipy import read_fits

from .config import cpism_refdata, S
from .optics import filter_throughput


# initial psf simulation
apm, apm_header = fits.getdata(
    f'{cpism_refdata}/optics/apm.fits', header=True)
actuator = read_fits(f'{cpism_refdata}/optics/actuator.fits')
surface_aberration = read_fits(
    f'{cpism_refdata}/optics/initial_phase_aberration.fits')

wavelength = 625e-9  # m
pupil_diameter = 2  # m
focal_length = pupil_diameter * 83
pupil_grid = make_pupil_grid(apm.shape[0], apm.shape[0] * apm_header['PHRATE'])
aperture = make_circular_aperture(pupil_diameter)(pupil_grid)
aperture = aperture * Field(apm.flatten(), pupil_grid)


emccd_pixel_size = 13e-6  # m
arcsec2rad = np.radians(1 / 3600)
emccd_pixel_scale = emccd_pixel_size / \
    (arcsec2rad * focal_length)  # arcsec / pixel
spatial_resolution = focal_length * wavelength / \
    pupil_diameter  # meter per airy disk

q = spatial_resolution / emccd_pixel_size  # pixels per airy disk
num_airy = 512 / q  # airy disk per frame (2 * 512 = 1024 pix)
focal_full_frame = make_focal_grid(
    q, num_airy, spatial_resolution=spatial_resolution)
prop_full_frame = FraunhoferPropagator(
    pupil_grid, focal_full_frame, focal_length)

num_airy = 128 / q  # make a small frame for the planets
focal_sub_frame = make_focal_grid(
    q, num_airy, spatial_resolution=spatial_resolution)
prop_sub_frame = FraunhoferPropagator(
    pupil_grid, focal_sub_frame, focal_length)

num_actuators_across = 32
# dm spacing is little smaller than pupil
actuator_spacing = 0.95 / 32 * pupil_diameter
influence_functions = make_xinetics_influence_functions(
    pupil_grid, num_actuators_across, actuator_spacing)
deformable_mirror = DeformableMirror(influence_functions)

aberration = SurfaceApodizer(
    surface_sag=surface_aberration.flatten(), refractive_index=-1)
# arbitrary distance for the aberration to propagate
aberration_distance = 80 * focal_length
aberration = SurfaceAberrationAtDistance(aberration, aberration_distance)


def simulate_psf(error_value, band, spectrum, nsample=5, cstar=True, aber_phase=None):

    filter = filter_throughput(band)
    wave = filter.wave
    throughput = filter.throughput
    min_wave = wave[0]
    max_wave = wave[-1]

    sed = []
    sed_center_wavelength = []
    for i_wave in range(nsample):
        d_wave = (max_wave - min_wave) / nsample
        wave0 = min_wave + i_wave * d_wave
        wave1 = min_wave + (i_wave + 1) * d_wave
        sed_center_wavelength.append((wave0 + wave1) / 2 * 1e-10)

        i_throughput = throughput.copy()
        i_throughput[(wave > wave1) | (wave < wave0)] = 0
        i_band = S.ArrayBandpass(wave, i_throughput, waveunits='angstrom')
        i_sed = (spectrum * i_band).integrate()
        sed.append(i_sed)

    error = np.random.normal(0, error_value*1e-9, actuator.shape)


    imgs = []
    deformable_mirror.actuators = actuator + error

    prop = prop_full_frame
    if not cstar:
        prop = prop_sub_frame

    for i_sample in range(nsample):
        wf = Wavefront(aperture, sed_center_wavelength[i_sample])
        wf = aberration(wf)
        if aber_phase is not None:
            other_error = SurfaceApodizer(
                surface_sag=aber_phase.flatten(), refractive_index=-1)
            wf = other_error(wf)

        img = prop(deformable_mirror(wf)).intensity.shaped
        imgs.append(img / img.sum() * sed[i_sample])

    return np.array(imgs).sum(axis=0)
