import importlib
import tensorflow as tf
from tensorflow_addons.utils.types import FloatTensorLike
from tensorflow_addons.optimizers.utils import is_variable_matched_by_regexes

from typeguard import typechecked
from typing import Union, Callable, Type, Optional, List

class DecoupledWeightDecayExtension:
    """This class allows to extend optimizers with decoupled weight decay.

    It implements the decoupled weight decay described by [Loshchilov & Hutter]
    (https://arxiv.org/pdf/1711.05101.pdf), in which the weight decay is
    decoupled from the optimization steps w.r.t. to the loss function.
    For SGD variants, this simplifies hyperparameter search since it decouples
    the settings of weight decay and learning rate.
    For adaptive gradient algorithms, it regularizes variables with large
    gradients more than L2 regularization would, which was shown to yield
    better training loss and generalization error in the paper above.

    This class alone is not an optimizer but rather extends existing
    optimizers with decoupled weight decay. We explicitly define the two
    examples used in the above paper (SGDW and AdamW), but in general this can
    extend any OptimizerX class by using
        `ExtendedCls = extend_with_decoupled_weight_decay(OptimizerX)`.
    Weight decay can then be set when instantiating the optimizer:
        `optimizerX = ExtendedCls(weight_decay=0.001, learning_rate=0.001)`.
    In order for it to work, it must be the first class the Optimizer with
    weight decay inherits from, e.g.

    ```python
    class AdamW(DecoupledWeightDecayExtension, tf.keras.optimizers.Adam):
      def __init__(self, weight_decay, *args, **kwargs):
        super(AdamW, self).__init__(weight_decay, *args, **kwargs).
    ```

    Note: this extension decays weights BEFORE applying the update based
    on the gradient, i.e. this extension only has the desired behaviour for
    optimizers which do not depend on the value of'var' in the update step!

    Note: when applying a decay to the learning rate, be sure to manually apply
    the decay to the `weight_decay` as well. For example:

    ```python
    step = tf.Variable(0, trainable=False)
    schedule = tf.optimizers.schedules.PiecewiseConstantDecay(
        [10000, 15000], [1e-0, 1e-1, 1e-2])
    # lr and wd can be a function or a tensor
    lr = 1e-1 * schedule(step)
    wd = lambda: 1e-4 * schedule(step)

    # ...

    optimizer = tfa.optimizers.AdamW(learning_rate=lr, weight_decay=wd)
    ```
    """

    @typechecked
    def __init__(
        self,
        weight_decay: Union[FloatTensorLike, Callable],
        exclude_from_weight_decay: Optional[List[str]] = None,
        **kwargs,
    ):
        """Extension class that adds weight decay to an optimizer.

        Args:
            weight_decay: A `Tensor`, a floating point value, or a schedule
                that is a `tf.keras.optimizers.schedules.LearningRateSchedule`
                to decay the variable by, in the update step.
            exclude_from_weight_decay: List of regex patterns of
              variables excluded from weight decay. Variables whose name
              contain a substring matching the pattern will be excluded.
              Note `decay_var_list` in `minimize` or `apply_gradients` takes
              priority over `exclude_from_weight_decay` if specified.
            **kwargs: Optional list or tuple or set of `Variable` objects to
                decay.
        """
        wd = kwargs.pop("weight_decay", weight_decay)
        super().__init__(**kwargs)
        self._decay_var_list = None  # is set in minimize or apply_gradients
        self._set_hyper("weight_decay", wd)
        self.exclude_from_weight_decay = exclude_from_weight_decay

    def get_config(self):
        config = super().get_config()
        config.update(
            {
                "weight_decay": self._serialize_hyperparameter("weight_decay"),
                "exclude_from_weight_decay": self.exclude_from_weight_decay,
            }
        )
        return config

    @classmethod
    def from_config(cls, config, custom_objects=None):
        # LR handling copied from optimizer_v2.OptimizerV2
        if "learning_rate" in config:
            if isinstance(config["learning_rate"], dict):
                config["learning_rate"] = tf.keras.optimizers.schedules.deserialize(
                    config["learning_rate"], custom_objects=custom_objects
                )

        if "weight_decay" in config:
            if isinstance(config["weight_decay"], dict):
                config["weight_decay"] = tf.keras.optimizers.schedules.deserialize(
                    config["weight_decay"], custom_objects=custom_objects
                )

        return cls(**config)

    def minimize(
        self,
        loss,
        var_list,
        grad_loss=None,
        name=None,
        decay_var_list=None,
        tape=None,
    ):
        """Minimize `loss` by updating `var_list`.

        This method simply computes gradient using `tf.GradientTape` and calls
        `apply_gradients()`. If you want to process the gradient before
        applying then call `tf.GradientTape` and `apply_gradients()` explicitly
        instead of using this function.

        Args:
            loss: `Tensor` or callable. If a callable, `loss` should take no
                arguments and return the value to minimize. If a `Tensor`, the
                `tape` argument must be passed.
            var_list: list or tuple of `Variable` objects to update to
                minimize `loss`, or a callable returning the list or tuple of
                `Variable` objects. Use callable when the variable list would
                otherwise be incomplete before `minimize` since the variables
                are created at the first time `loss` is called.
            grad_loss: Optional. A `Tensor` holding the gradient computed for
                `loss`.
            decay_var_list: Optional list of variables to be decayed. Defaults
                to all variables in var_list. Note `decay_var_list` takes
                priority over `exclude_from_weight_decay` if specified.
            name: Optional name for the returned operation.
            tape: (Optional) `tf.GradientTape`. If `loss` is provided as a
                `Tensor`, the tape that computed the `loss` must be provided.
        Returns:
            An Operation that updates the variables in `var_list`.
        Raises:
            ValueError: If some of the variables are not `Variable` objects.
        """
        self._set_decay_var_list(var_list, decay_var_list)
        return super().minimize(
            loss, var_list=var_list, grad_loss=grad_loss, name=name, tape=tape
        )

    def apply_gradients(self, grads_and_vars, name=None, decay_var_list=None, **kwargs):
        """Apply gradients to variables.

        This is the second part of `minimize()`. It returns an `Operation` that
        applies gradients.

        Args:
            grads_and_vars: List of (gradient, variable) pairs.
            name: Optional name for the returned operation. Default to the
                name passed to the `Optimizer` constructor.
            decay_var_list: Optional list of variables to be decayed. Defaults
                to all variables in var_list. Note `decay_var_list` takes
                priority over `exclude_from_weight_decay` if specified.
            **kwargs: Additional arguments to pass to the base optimizer's
                apply_gradient method, e.g., TF2.2 added an argument
                `experimental_aggregate_gradients`.
        Returns:
            An `Operation` that applies the specified gradients.
        Raises:
            TypeError: If `grads_and_vars` is malformed.
            ValueError: If none of the variables have gradients.
        """
        grads_and_vars = list(grads_and_vars)
        self._set_decay_var_list((v for _, v in grads_and_vars), decay_var_list)
        return super().apply_gradients(grads_and_vars, name=name, **kwargs)

    def _decay_weights_op(self, var, apply_state=None):
        if self._do_use_weight_decay(var):
            var_device, var_dtype = var.device, var.dtype.base_dtype
            coefficients = (apply_state or {}).get(
                (var_device, var_dtype)
            ) or self._fallback_apply_state(var_device, var_dtype)

            return var.assign_sub(coefficients["wd_t"] * var, self._use_locking)
        return tf.no_op()

    def _decay_weights_sparse_op(self, var, indices, apply_state=None):
        if self._do_use_weight_decay(var):
            var_device, var_dtype = var.device, var.dtype.base_dtype
            coefficients = (apply_state or {}).get(
                (var_device, var_dtype)
            ) or self._fallback_apply_state(var_device, var_dtype)

            update = -coefficients["wd_t"] * tf.gather(var, indices)
            return self._resource_scatter_add(var, indices, update)
        return tf.no_op()

    def _prepare_local(self, var_device, var_dtype, apply_state):
        super(DecoupledWeightDecayExtension, self)._prepare_local(
            var_device, var_dtype, apply_state
        )

        if "weight_decay" in self._hyper:
            wd_t = tf.identity(self._decayed_wd(var_dtype))
            apply_state[(var_device, var_dtype)]["wd_t"] = wd_t

    def _decayed_wd(self, var_dtype):
        wd_t = self._get_hyper("weight_decay", var_dtype)

        if isinstance(wd_t, tf.keras.optimizers.schedules.LearningRateSchedule):
            wd_t = tf.cast(wd_t(self.iterations), var_dtype)

        return wd_t

    # Here, we overwrite the apply functions that the base optimizer calls.
    # super().apply_x resolves to the apply_x function of the BaseOptimizer.

    def _resource_apply_dense(self, grad, var, apply_state=None):
        with tf.control_dependencies(
            [self._decay_weights_op(var, apply_state=apply_state)]
        ):
            return super()._resource_apply_dense(grad, var, apply_state=apply_state)

    def _resource_apply_sparse(self, grad, var, indices, apply_state=None):
        decay_op = self._decay_weights_sparse_op(var, indices, apply_state=apply_state)
        with tf.control_dependencies([decay_op]):
            return super()._resource_apply_sparse(
                grad, var, indices, apply_state=apply_state
            )

    def _set_decay_var_list(self, var_list, decay_var_list=None):
        if decay_var_list:
            self._decay_var_list = set(v.ref() for v in decay_var_list)
        elif self.exclude_from_weight_decay:
            self._decay_var_list = set(
                v.ref()
                for v in var_list
                if not is_variable_matched_by_regexes(v, self.exclude_from_weight_decay)
            )
        else:
            self._decay_var_list = None

    def _do_use_weight_decay(self, var):
        """Whether to use L2 weight decay for `var`."""
        if self._decay_var_list is None:
            return True
        return var.ref() in self._decay_var_list

if importlib.util.find_spec("tensorflow.keras.optimizers.legacy") is not None:
    keras_legacy_optimizer = Union[
        tf.keras.optimizers.legacy.Optimizer, tf.keras.optimizers.Optimizer
    ]
else:
    keras_legacy_optimizer = tf.keras.optimizers.Optimizer

@typechecked
def extend_with_decoupled_weight_decay(
    base_optimizer: Type[keras_legacy_optimizer],
) -> Type[keras_legacy_optimizer]:
    """Factory function returning an optimizer class with decoupled weight
    decay.

    Returns an optimizer class. An instance of the returned class computes the
    update step of `base_optimizer` and additionally decays the weights.
    E.g., the class returned by
    `extend_with_decoupled_weight_decay(tf.keras.optimizers.Adam)` is
    equivalent to `tfa.optimizers.AdamW`.

    The API of the new optimizer class slightly differs from the API of the
    base optimizer:
    - The first argument to the constructor is the weight decay rate.
    - Optional keyword argument `exclude_from_weight_decay` accepts list of
      regex patterns of variables excluded from weight decay. Variables whose
      name contain a substring matching the pattern will be excluded.
    - `minimize` and `apply_gradients` accept the optional keyword argument
      `decay_var_list`, which specifies the variables that should be decayed.
      Note this takes priority over `exclude_from_weight_decay` if specified.
      If both `None`, all variables that are optimized are decayed.

    Usage example:
    ```python
    # MyAdamW is a new class
    MyAdamW = extend_with_decoupled_weight_decay(tf.keras.optimizers.Adam)
    # Create a MyAdamW object
    optimizer = MyAdamW(weight_decay=0.001, learning_rate=0.001)
    # update var1, var2 but only decay var1
    optimizer.minimize(loss, var_list=[var1, var2], decay_variables=[var1])

    Note: this extension decays weights BEFORE applying the update based
    on the gradient, i.e. this extension only has the desired behaviour for
    optimizers which do not depend on the value of 'var' in the update step!

    Note: when applying a decay to the learning rate, be sure to manually apply
    the decay to the `weight_decay` as well. For example:

    ```python
    step = tf.Variable(0, trainable=False)
    schedule = tf.optimizers.schedules.PiecewiseConstantDecay(
        [10000, 15000], [1e-0, 1e-1, 1e-2])
    # lr and wd can be a function or a tensor
    lr = 1e-1 * schedule(step)
    wd = lambda: 1e-4 * schedule(step)

    # ...

    optimizer = tfa.optimizers.AdamW(learning_rate=lr, weight_decay=wd)
    ```

    Note: you might want to register your own custom optimizer using
    `tf.keras.utils.get_custom_objects()`.

    Args:
        base_optimizer: An optimizer class that inherits from
            tf.optimizers.Optimizer.

    Returns:
        A new optimizer class that inherits from DecoupledWeightDecayExtension
        and base_optimizer.
    """

    class OptimizerWithDecoupledWeightDecay(
        DecoupledWeightDecayExtension, base_optimizer
    ):
        """Base_optimizer with decoupled weight decay.

        This class computes the update step of `base_optimizer` and
        additionally decays the variable with the weight decay being
        decoupled from the optimization steps w.r.t. to the loss
        function, as described by [Loshchilov & Hutter]
        (https://arxiv.org/pdf/1711.05101.pdf). For SGD variants, this
        simplifies hyperparameter search since it decouples the settings
        of weight decay and learning rate. For adaptive gradient
        algorithms, it regularizes variables with large gradients more
        than L2 regularization would, which was shown to yield better
        training loss and generalization error in the paper above.
        """

        @typechecked
        def __init__(
            self,
            weight_decay: Union[FloatTensorLike, Callable],
            *args,
            **kwargs,
        ):
            # super delegation is necessary here
            super().__init__(weight_decay, *args, **kwargs)

    return OptimizerWithDecoupledWeightDecay

if hasattr(tf.keras.optimizers, "legacy"):
    ADAM_CLASS = tf.keras.optimizers.legacy.Adam
    SGD_CLASS = tf.keras.optimizers.legacy.SGD
else:
    ADAM_CLASS = tf.keras.optimizers.Adam
    SGD_CLASS = tf.keras.optimizers.SGD

@tf.keras.utils.register_keras_serializable(package="Addons")
class SGDW(DecoupledWeightDecayExtension, SGD_CLASS):
    """Optimizer that implements the Momentum algorithm with weight_decay.

    This is an implementation of the SGDW optimizer described in "Decoupled
    Weight Decay Regularization" by [Loshchilov & Hutter]
    (https://arxiv.org/pdf/1711.05101.pdf).
    It computes the update step of `tf.keras.optimizers.SGD` and additionally
    decays the variable. Note that this is different from adding
    L2 regularization on the variables to the loss. Decoupling the weight decay
    from other hyperparameters (in particular the learning rate) simplifies
    hyperparameter search.

    For further information see the documentation of the SGD Optimizer.

    This optimizer can also be instantiated as
    ```python
    extend_with_decoupled_weight_decay(tf.keras.optimizers.SGD,
                                       weight_decay=weight_decay)
    ```

    Note: when applying a decay to the learning rate, be sure to manually apply
    the decay to the `weight_decay` as well. For example:

    ```python
    step = tf.Variable(0, trainable=False)
    schedule = tf.optimizers.schedules.PiecewiseConstantDecay(
        [10000, 15000], [1e-0, 1e-1, 1e-2])
    # lr and wd can be a function or a tensor
    lr = 1e-1 * schedule(step)
    wd = lambda: 1e-4 * schedule(step)

    # ...

    optimizer = tfa.optimizers.SGDW(
        learning_rate=lr, weight_decay=wd, momentum=0.9)
    ```
    """

    @typechecked
    def __init__(
        self,
        weight_decay: Union[FloatTensorLike, Callable],
        learning_rate: Union[FloatTensorLike, Callable] = 0.001,
        momentum: Union[FloatTensorLike, Callable] = 0.0,
        nesterov: bool = False,
        name: str = "SGDW",
        **kwargs,
    ):
        """Construct a new SGDW optimizer.

        For further information see the documentation of the SGD Optimizer.

        Args:
            learning_rate: float hyperparameter >= 0. Learning rate.
            momentum: float hyperparameter >= 0 that accelerates SGD in the
                relevant direction and dampens oscillations.
            nesterov: boolean. Whether to apply Nesterov momentum.
            name: Optional name prefix for the operations created when applying
                gradients.  Defaults to 'SGD'.
            **kwargs: keyword arguments. Allowed to be {`clipnorm`, `clipvalue`,
                `lr`, `decay`, `exclude_from_weight_decay`}. `clipnorm` is clip
                gradients by norm; `clipvalue` is clip gradients by value.
                `decay` is included for backward compatibility to allow time
                inverse decay of learning rate. `lr` is included for backward
                compatibility, recommended to use `learning_rate` instead.
                `exclude_from_weight_decay` accepts list of regex patterns of
                variables excluded from weight decay.
        """
        super().__init__(
            weight_decay,
            learning_rate=learning_rate,
            momentum=momentum,
            nesterov=nesterov,
            name=name,
            **kwargs,
        )

@tf.keras.utils.register_keras_serializable(package="Addons")
class AdamW(DecoupledWeightDecayExtension, ADAM_CLASS):
    """Optimizer that implements the Adam algorithm with weight decay.

    This is an implementation of the AdamW optimizer described in "Decoupled
    Weight Decay Regularization" by [Loshchilov & Hutter]
    (https://arxiv.org/pdf/1711.05101.pdf).

    It computes the update step of `tf.keras.optimizers.Adam` and additionally
    decays the variable. Note that this is different from adding L2
    regularization on the variables to the loss: it regularizes variables with
    large gradients more than L2 regularization would, which was shown to yield
    better training loss and generalization error in the paper above.

    For further information see the documentation of the Adam Optimizer.

    This optimizer can also be instantiated as
    ```python
    extend_with_decoupled_weight_decay(tf.keras.optimizers.Adam,
                                       weight_decay=weight_decay)
    ```

    Note: when applying a decay to the learning rate, be sure to manually apply
    the decay to the `weight_decay` as well. For example:

    ```python
    step = tf.Variable(0, trainable=False)
    schedule = tf.optimizers.schedules.PiecewiseConstantDecay(
        [10000, 15000], [1e-0, 1e-1, 1e-2])
    # lr and wd can be a function or a tensor
    lr = 1e-1 * schedule(step)
    wd = lambda: 1e-4 * schedule(step)

    # ...

    optimizer = tfa.optimizers.AdamW(learning_rate=lr, weight_decay=wd)
    ```
    """

    @typechecked
    def __init__(
        self,
        weight_decay: Union[FloatTensorLike, Callable],
        learning_rate: Union[FloatTensorLike, Callable] = 0.001,
        beta_1: Union[FloatTensorLike, Callable] = 0.9,
        beta_2: Union[FloatTensorLike, Callable] = 0.999,
        epsilon: FloatTensorLike = 1e-07,
        amsgrad: bool = False,
        name: str = "AdamW",
        **kwargs,
    ):
        """Construct a new AdamW optimizer.

        For further information see the documentation of the Adam Optimizer.

        Args:
            weight_decay: A Tensor or a floating point value. The weight decay.
            learning_rate: A Tensor or a floating point value. The learning
                rate.
            beta_1: A float value or a constant float tensor. The exponential
                decay rate for the 1st moment estimates.
            beta_2: A float value or a constant float tensor. The exponential
                decay rate for the 2nd moment estimates.
            epsilon: A small constant for numerical stability. This epsilon is
                "epsilon hat" in the Kingma and Ba paper (in the formula just
                before Section 2.1), not the epsilon in Algorithm 1 of the
                paper.
            amsgrad: boolean. Whether to apply AMSGrad variant of this
                algorithm from the paper "On the Convergence of Adam and
                beyond".
            name: Optional name for the operations created when applying
                gradients. Defaults to "AdamW".
            **kwargs: keyword arguments. Allowed to be {`clipnorm`, `clipvalue`,
                `lr`, `decay`, `exclude_from_weight_decay`}. `clipnorm` is clip
                gradients by norm; `clipvalue` is clip gradients by value.
                `decay` is included for backward compatibility to allow time
                inverse decay of learning rate. `lr` is included for backward
                compatibility, recommended to use `learning_rate` instead.
                `exclude_from_weight_decay` accepts list of regex patterns of
                variables excluded from weight decay.
        """
        super().__init__(
            weight_decay,
            learning_rate=learning_rate,
            beta_1=beta_1,
            beta_2=beta_2,
            epsilon=epsilon,
            amsgrad=amsgrad,
            name=name,
            **kwargs,
        )


import datetime
import time
#import subprocess
import os
import platform
from pathlib import Path
from astropy.io import fits
from astropy import units as u
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
import scipy.optimize as so
import numpy as np
#import matplotlib.pyplot as plt
os_name=platform.system()

import warnings
from astropy.utils.exceptions import AstropyWarning
warnings.simplefilter('ignore', category=AstropyWarning)

from tan_module import *
from save_solve_pars import *

if os_name=='Windows':
    solve_field_path='/usr/local/astrometry/bin/solve-field'
    image2xypath='/usr/local/astrometry/bin/image2xy'
else:
    solve_field_path='solve-field'
    image2xypath='image2xy'

win_com_prefix='bash --login -c "('
win_com_postfix=')"'
s1c_solve_pars='--scale-units arcsecperpix --scale-low 250 --scale-high 270'

def s1c_get_date_obs(filename,ut_shift=-3):
    fdt=open(filename,'r',errors = 'ignore')
    fdt.seek(8*80)
    test=fdt.read(39)
    fdt.close()
    test=test[15::]
    ms=test[16:20]
    if ms=='1000':
        test=test[0:16]+'000 2014'
#         test[16]='0'
        obs_time=datetime.datetime.strptime(test,'%b %d %H:%M:%S.%f %Y')+datetime.timedelta(hours=ut_shift)+datetime.timedelta(seconds=1)
    else:
        obs_time=datetime.datetime.strptime(test,'%b %d %H:%M:%S.%f %Y')+datetime.timedelta(hours=ut_shift)
    return obs_time

def image2xy(fname, out_dir):
    os_name=platform.system()
#     if not os.path.exists(out_dir):
#         os.makedirs(out_dir)
    name=fname.split('/')[-1]
    out_name=out_dir + '/' + name.split('.')[-2] + '.axy'
    com_line=image2xypath +' -O -o ' + out_name +' ' + fname
    if os_name=='Windows':
        com_line=win_com_prefix+com_line+win_com_postfix
    #print(com_line)
    return  os.system(com_line), out_name
# image2xy(fname, spath)

def s1c_solve_field_altaz(fname, solve_pars, get_date_obs_fun=s1c_get_date_obs,lat_deg=56.1501667,lon_deg=46.1050833,hei_m=183.):
    os_name=platform.system()

    fname=os.path.abspath(fname)
#     print(fname)
    path, file = os.path.split(fname)
    spath=path+"/.temp"
    fname_axy_abs=spath+"/"+file[:-4]+".axy"
    if not os.path.exists(spath):
        os.makedirs(spath)

    if os_name=='Windows':
        fname="/cygdrive/"+fname.replace(":","").replace("\\","/")
    #~ print(fname)
    path, file = os.path.split(fname)
    spath=path+"/.temp"
#     print(spath)

    err_code, axy_fname = image2xy(fname, spath)

    if err_code!=0:
        return 1

    #~ com_line='cd ' + spath  + ' && ' + solve_field_path + ' ' + axy_fname.split('/')[-1] +' --continue -D ' + spath + ' ' + solve_pars + ' --cpulimit 2 --no-plots -M none -S none -B none -W none'
    com_line='cd ' + spath  + ' && ' + solve_field_path + ' ' + fname +' --overwrite -D ' + spath + ' ' + solve_pars + ' --cpulimit 2 --no-plots -M none -N none -S none -B none -W none'
    if os_name=='Windows':
        com_line=win_com_prefix+com_line+win_com_postfix
    #~ print(com_line)
    err_code=os.system(com_line)
    if err_code!=0:
        return 2

    site=EarthLocation(lat=lat_deg*u.deg, lon=lon_deg*u.deg, height=hei_m*u.m)

    fname_xy=axy_fname[0:-4]+'-indx.xyls'
    fname_rd=axy_fname[0:-4]+'.rdls'

    if os_name=='Windows':
        fname=fname[10::]
        fname=fname[0]+':'+fname[1::]
        fname_xy=fname_xy[10::]
        fname_xy=fname_xy[0]+':'+fname_xy[1::]
        fname_rd=fname_rd[10::]
        fname_rd=fname_rd[0]+':'+fname_rd[1::]
#     print(fname_xy)
#     print(fname_rd)
#     print(axy_fname)
    if Path(fname_rd).is_file()==False:
        return np.nan,np.nan,np.array([np.nan, np.nan, np.nan]),np.array([np.nan, np.nan, np.nan]), np.array([np.nan, np.nan, np.nan]),np.array([np.nan, np.nan, np.nan])

    date_obs=get_date_obs_fun(fname,ut_shift=-4)

    hdulist = fits.open(fname_xy)

    tbdata = hdulist[1].data

    X=tbdata.field('X')
    Y=tbdata.field('Y')

    hdulist.close()

    hdulist = fits.open(fname_rd)

    tbdata = hdulist[1].data

    RA=tbdata.field('RA')
    DEC=tbdata.field('DEC')
    hdulist.close()

    SC=SkyCoord(RA, DEC, frame='icrs', unit='deg');

    AA=SC.transform_to(AltAz(obstime=date_obs, location=site,temperature=0*u.deg_C,pressure=1013*u.hPa,
                                           relative_humidity=0.5,obswl=630.0*u.nm))

    AZ=AA.az.rad
    ALT=AA.alt.rad

    res=so.minimize(tan_calc_pix2st_coefs_discrep,(0,np.pi/2),(AZ,ALT,X,Y),method='Nelder-Mead')
    res_fun=res.fun

    res_x=res.x
    if res_x[1]>np.pi/2:
        res_x[1]=np.pi-res_x[1]
        res_x[0]=res_x[0]+np.pi
    if res_x[0]<0:
        res_x[0]=res_x[0]+2*np.pi
    if res_x[0]>2*np.pi:
        res_x[0]=res_x[0]%(2*np.pi)

    az0=res_x[0];
    alt0=res_x[1];

    a,b,c,d=tan_calc_pix2st_coefs((az0,alt0),AZ,ALT,X,Y)
#     print(az0*180/np.pi,alt0*180/np.pi)
    az_c, alt_c = tan_pix2hor(144,144,az0,alt0,a,b)
    print("Central pixel direction (AZ, ALT in deg):",az_c*180/np.pi,alt_c*180/np.pi)
    return az0,alt0,az_c,alt_c,a,b,c,d,res_fun

def save_solve_data(fit_path,solve_fname):
    fit_filenames=[fit_path+'/'+fn for fn in next(os.walk(fit_path))[2]]
    res_fid=open(solve_fname,'w')
    res_fid.write("# filename.fit error_pix az_c alt_c az0 alt0 a[0] a[1] a[2] b[0] b[1] b[2] c[0] c[1] c[2] d[0] d[1] d[2]\n")
    for i in range(len(fit_filenames)):
        ret = s1c_solve_field_altaz(fit_filenames[i],s1c_solve_pars)
        if type(ret)!=int:
            az0=ret[0]
            alt0=ret[1]
            az_c=ret[2]
            alt_c=ret[3]
            a=ret[4]
            b=ret[5]
            c=ret[6]
            d=ret[7]
            fun=ret[8]

            str_to_file=fit_filenames[i].split("/")[-1]+ " " + str(fun) + " " + str(az_c) + " " +str(alt_c)+ " " +str(az0)+ " " +str(alt0)
            str_to_file+=" " + str(a[0]) + " " + str(a[1]) + " " + str(a[2])
            str_to_file+=" " + str(b[0]) + " " + str(b[1]) + " " + str(b[2])
            str_to_file+=" " + str(c[0]) + " " + str(c[1]) + " " + str(c[2])
            str_to_file+=" " + str(d[0]) + " " + str(d[1]) + " " + str(d[2]) + "\n"
            res_fid.write(str_to_file)
        else:
            print(ret)
            print(str(i)," ",fit_filenames[i].split("/")[-1]," ERROR")
    res_fid.close()

fit_path="../data/140824/s1c"
solve_fname="solve_field_altaz_140824_s1c.dat"
save_fname="s1c_140824_solve.pars"
save_solve_data(fit_path,solve_fname)
save_med_solve_pars(solve_fname,save_fname)

fit_path="../data/140826/s1c"
solve_fname="solve_field_altaz_140826_s1c.dat"
save_fname="s1c_140826_solve.pars"
save_solve_data(fit_path,solve_fname)
save_med_solve_pars(solve_fname,save_fname)


from apps.category.models import Category
from apps.category.serializers import CategorySerializer
from rest_framework import serializers

from .models import *

class SellerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Sellers
        fields =[
            'author',
            "address",
            'polygon_address',
            # 'course',
        ]

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Image
        fields =[
            'id',
            'position_id',
            "title",
            'file',
            'course',
        ]

class VideoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Video
        fields =[
            'id',
            'position_id',
            "title",
            'file',
            'course',
        ]

class WhatLearntSerializer(serializers.ModelSerializer):
    id=serializers.CharField(source='position_id')
    class Meta:
        model= WhatLearnt
        fields = [
            "id",
            "position_id",
            "title",
        ]

class CoursesListSerializer(serializers.ModelSerializer):
    # best_seller=serializers.BooleanField(source='get_best_seller')
    thumbnail = serializers.ImageField(source='first_image')
    category=serializers.CharField(source='get_category_name')
    student_rating=serializers.IntegerField(source='get_rating')
    student_rating_no=serializers.IntegerField(source='get_no_rating')
    class Meta:
        model=Course
        fields=[
            "id",
            "token_id",
            "nft_address",
            "title",
            "thumbnail",
            "description",
            'short_description',
            "language",
            "best_seller",
            "published",
            'payment',
            'slug',
            'price',
            'compare_price',
            'discount_until',
            'discount',
            'student_rating',
            'student_rating_no',
            'category',
        ]

class CourseSerializer(serializers.ModelSerializer):
    total_lectures=serializers.IntegerField(source="get_total_lectures")
    videos=VideoSerializer(many=True)
    images=ImageSerializer(many=True)
    total_duration=serializers.CharField(source='total_course_length')
    student_rating=serializers.IntegerField(source='get_rating')
    student_rating_no=serializers.IntegerField(source='get_no_rating')
    category=CategorySerializer()
    sellers=SellerSerializer(many=True)
    class Meta:
        model=Course
        fields=[
            "id",
            "token_id",
            "nft_address",
            "author",
            "sellers",
            "title",
            "thumbnail",
            "description",
            "short_description",
            "sales_video",
            "published",
            "updated",
            "course_length",
            "language",
            "level",
            "taught",
            "best_seller",
            'status',
            "total_lectures",
            "total_duration",
            "category",
            'student_rating',
            'student_rating_no',
            'payment',
            'price',
            'stock',
            'compare_price',
            'discount_until',
            'discount',
            'keywords',
            'slug',
            'views',
            'students',
            'goals',
            'course_structure',
            'setup',
            'film',
            'curriculum',
            'captions',
            'accessibility',
            'landing_page',
            'pricing',
            'promotions',
            'allow_messages',
            'welcome_message',
            'congrats_message',
            'slug_changes',
            'progress',
            'can_delete',
            'banned',
            'videos',
            'images',
        ]

class CoursesManageListSerializer(serializers.ModelSerializer):
    # best_seller=serializers.BooleanField(source='get_best_seller')
    thumbnail=serializers.BooleanField(source='first_image')
    category=serializers.CharField(source='get_category_name')
    videos=VideoSerializer(many=True)
    images=ImageSerializer(many=True)
    student_rating=serializers.IntegerField(source='get_rating')
    student_rating_no=serializers.IntegerField(source='get_no_rating')
    class Meta:
        model=Course
        fields=[
            "id",
            "nft_address",
            "author",
            "title",
            "thumbnail",
            "description",
            'short_description',
            "language",
            "best_seller",
            "status",
            "published",
            'payment',
            'slug',
            'price',
            'stock',
            'student_rating',
            'student_rating_no',
            'compare_price',
            'discount_until',
            'discount',
            'category',
            'progress',
            'videos',
            'images',
        ]

class WhatLearntSerializer(serializers.ModelSerializer):
    class Meta:
        model= WhatLearnt
        fields = [
            "id",
            "position_id",
            "title",
        ]

class RequisiteSerializer(serializers.ModelSerializer):
    class Meta:
        model= Requisite
        fields = [
            "id",
            "position_id",
            "title",
        ]

class WhoIsForSerializer(serializers.ModelSerializer):
    class Meta:
        model= WhoIsFor
        fields = [
            "id",
            "position_id",
            "title",
        ]

class ResourceSerializer(serializers.ModelSerializer):
    # file=serializers.CharField(source='get_absolute_url')
    class Meta:
        model= Resource
        fields = [
            "title",
            "file",
            "url",
            "id"
        ]

class EpisodePaidSerializer(serializers.ModelSerializer):
    length=serializers.CharField(source='get_video_length_time')
    # file=serializers.CharField(source='get_absolute_url')
    resources=ResourceSerializer(many=True)
    # comments=CommentSerializer(many=True)
    class Meta:
        model = Episode
        fields = [
            'id',
            "number",
            "title",
            "file",
            "filename",
            "date",
            "resources",
            "content",
            "description",
            "section_uuid",
            'published',
            "length",
        ]

class CourseSectionPaidSerializer(serializers.ModelSerializer):
    episodes=EpisodePaidSerializer(many=True)
    total_duration=serializers.CharField(source='total_length')
    class Meta:
        model=Section
        fields=[
            'id',
            'title',
            'learning_objective',
            'number',
            'episodes',
            'user',
            'published',
            'course',
            'total_duration'
        ]

class EpisodeUnPaidSerializer(serializers.ModelSerializer):
    length=serializers.CharField(source='get_video_length_time')
    # file=serializers.CharField(source='get_absolute_url')
    # resources=ResourceSerializer(many=True)
    # comments=CommentSerializer(many=True)
    class Meta:
        model = Episode
        fields = [
            'id',
            "number",
            "title",
            "date",
            "description",
            "section_uuid",
            'published',
            "length",
        ]

class CourseSectionUnPaidSerializer(serializers.ModelSerializer):
    episodes=EpisodeUnPaidSerializer(many=True)
    total_duration=serializers.CharField(source='total_length')
    class Meta:
        model=Section
        fields=[
            'id',
            'title',
            'learning_objective',
            'number',
            'episodes',
            'user',
            'published',
            'course',
            'total_duration'
        ]

class LikeSerializer(serializers.ModelSerializer):
    class Meta:
        model=Question
        fields=[
            'id',
            'user',
        ]

class AnswerSerializer(serializers.ModelSerializer):
    # question=QuestionSerializer()
    likes=LikeSerializer(many=True)
    # dislikes=serializers.CharField(source='dislikes_count')
    class Meta:
        model=Answer
        fields=[
            'id',
            'user',
            'body',
            'created_date',
            'update_date',
            'is_accepted_answer',
            'likes',
            # 'dislikes',
        ]

class QuestionSerializer(serializers.ModelSerializer):
    episode=EpisodePaidSerializer()
    likes=LikeSerializer(many=True)
    count=serializers.CharField(source='likes_count')
    dislikes=serializers.CharField(source='dislikes_count')
    answers_count=serializers.CharField(source='get_answers_count')
    # answers=serializers.CharField(source='get_answers')
    correct_answer = AnswerSerializer()
    class Meta:
        model=Question
        fields=[
            'id',
            'user',
            'title',
            'body',
            'created_date',
            'correct_answer',
            'update_date',
            'has_accepted_answer',
            'episode',
            'likes',
            'count',
            'dislikes',
            'answers_count',
        ]

class PaidSerializer(serializers.ModelSerializer):
    courses=CoursesListSerializer(many=True)
    class Meta:
        model=Paid
        fields=[
            'id',
            'user',
            'courses',
            'date_created',
        ]