#! /usr/bin/env python
__author__ = 'Mathias Zechmeister'
__version__ = '2019-03-01'

import datetime
import glob
import gzip
import os
import sys
import tarfile
import time
import warnings
from collections import namedtuple


try:
   import astropy.io.fits as pyfits
except:
   import pyfits
#import fitsio
import numpy as np

import brv_we14idl
import brv_we14html
from pause import pause, stop
from gplot import *
import sunrise

class nameddict(dict):
   """
   Examples
   --------
   >>> nameddict({'a':1, 'b':2})
   {'a': 1, 'b': 2}
   >>> x = nameddict(a=1, b=2)
   >>> x.a
   1
   >>> x['a']
   1
   >>> x.translate(3)
   ['a', 'b']

   """
   __getattr__ = dict.__getitem__

   def translate(self, x):
      return [name for name,f in self.items() if (f & x) or f==x==0]

# bpmap flagging
flag = nameddict(
   ok=       0, # good pixel
   nan=      1, # nan flux in pixel of spectrum
   neg=      2, # significant negative flux in pixel of spectrum (f < -3*f_err < 0 && )
   sat=      4, # too high flux (saturated)
   atm=      8, # telluric at wavelength of spectrum
   sky=     16, # sky emission at wavelength of spectrum
   out=     32, # region outside the template
   clip=    64, # clipped value
   lowQ=   128, # low Q in stellar spectrum (mask continuum)
   badT=   256, # bad corresponding region in the template spectrum
)

# spectrum flagging
sflag = nameddict(
   nosci=    1, # nosci frame, e.g. calibration files
   iod=      2,
   config=   4, # bad configuration/setup (e.g. HARPS eggs, pre, post)
   dist=    16, # coordinates too much off
   daytime= 32, # not within nautical twilight
   lowSN=   64, # too low S/N
   hiSN=   128, # too high S/N
   led=    256, # LED on during observation (CARM_VIS)
   rvnan=  512
)


# bpmap flags
flag_cosm = flag.sat  # @ FEROS for now use same flag as sat
def_wlog = True
brvrefs = ['DRS', 'MH', 'WEhtml', 'WEidl', 'WE']

class Spectrum:
   """
   Compiles information from frame (filesname), instrument and target.
   and provides method to read the data.

   Examples
   --------
   >>> from read_spec import *
   >>> x = Spectrum('/media/5748244b-c6d4-49bb-97d4-1ae0a8ba6aed/data/HARPS/red_DRS/gj551/HARPS.2013-05-06T01:54:55.600.tar', fib='A',orders=-1,drs=1)
   >>> x = Spectrum('/home/zechmeister/data/harps/gj588/data/HARPS.2013-05-11T08:46:58.520_e2ds_A.fits')

   """
   brvref = 'MH' # can be overwritten by serval.py depending on user input
   def __init__(self, filename, inst='HARPS', pfits=True, orders=None, wlog=def_wlog, drs=False, fib=None, targ=None, verb=False):
      """
      pfits : fits reader
             True: pyfits (safe, e.g. to get the full header of the start refence spectrum)
             2: my own simple pure python fits reader, faster than pyfits
             It was developed for HARPS e2ds, but the advent ADP.*.tar was difficult.

      """
      self.filename = filename
      self.drsname = 'DRS'
      self.drs = drs
      self.flag = 0
      self.bflag = 0
      self.tmmean = 0
      self.f = None
      self.w = None
      self.e = None
      self.bpmap = None
      self.mod = None
      self.fib = fib
      self.fo = None
      self.wo = None
      self.eo = None
      self.bo = None
      self.drift = np.nan
      self.e_drift = np.nan
      self.utc = None
      self.ra = None
      self.de = None
#      self.obs = type('specdata', (object,), {'lat': None, 'lon': None})
      self.obs = type('specdata', (object,), dict(lat=None, lon=None))
      self.airmass = np.nan
      self.inst = inst
      self.scan = self.inst.scan
      self.data = self.inst.data

      if '.gz' in filename: pfits=True

      self.ccf = type('ccf',(), dict(rvc=np.nan, err_rvc=np.nan, bis=np.nan, fwhm=np.nan, contrast=np.nan, mask=0, header=0))

      # scan fits header for times, modes, snr, etc.
      #read_spec(self, filename, inst=inst, pfits=pfits, verb=verb)
      self.scan(self, filename, pfits=pfits)

      if verb:
         print "scan %s:"%self.instname, self.timeid, self.header['OBJECT'], self.drsbjd, self.sn55, self.drsberv, self.drift, self.flag, self.calmode

      if inst.name != self.instname:
         pause('WARNING:', filename, 'from', inst, ', but mode is', self.instname)
      self.obj = self.header['OBJECT']

      ### Barycentric correction ###
      # start and end computed only for WE and WEhtml
      self.bjd_sart, self.berv_start = np.nan, np.nan
      self.bjd_end, self.berv_end = np.nan, np.nan

      if targ and targ.name == 'cal':
         self.bjd, self.berv = self.drsbjd, 0.
      elif targ and targ.ra:  # unique coordinates
         obsloc = inst.obsloc if hasattr(inst, 'obsloc') else {}
         if self.brvref == 'MH':
            # fastest version
            #sys.path.append(os.environ['HOME']+'/programs/BarCor/')
            sys.path.insert(1, sys.path[0]+os.sep+'BarCor')            # bary now in src/BarCor
            import bary
            self.bjd, self.berv = bary.bary(self.dateobs, targ.ra, targ.de, inst.name, epoch=2000, exptime=self.exptime*2* self.tmmean, pma=targ.pmra, pmd=targ.pmde, obsloc=obsloc)
         if self.brvref in ('WEhtml', 'WEidl', 'WE'):
            # Wright & Eastman (2014) via online or idl request
            # cd /home/raid0/zechmeister/idl/exofast/bary
            # export ASTRO_DATA=/home/raid0/zechmeister/
            #idl -e 'print, bjd2utc(2457395.24563d, 4.58559072, 44.02195596), fo="(D)"'
            jd_utc = [self.mjd + 2400000.5 + self.exptime*self.tmmean/24./3600]
            jd_utcs = [self.mjd + 2400000.5, jd_utc[0], self.mjd + 2400000.5 + self.exptime/24./3600]
            ra = (targ.ra[0] + targ.ra[1]/60. + targ.ra[2]/3600.) * 15  # [deg]
            de = (targ.de[0] + np.copysign(targ.de[1]/60. + targ.de[2]/3600., targ.de[0]))       # [deg]
            obsname = inst.obsname #{'CARM_VIS':'ca', 'CARM_NIR':'ca', 'FEROS':'eso', 'HARPS':'eso', 'HARPN':'lapalma', 'HPF':'hpf'}[inst]
            if self.brvref == 'WE':
               # pure python version
               import brv_we14py
               #self.bjd, self.berv = brv_we14py.bjdbrv(jd_utc=jd_utc[0], ra=ra, dec=de, obsname=obsname, pmra=targ.pmra, pmdec=targ.pmde, parallax=0., rv=0., zmeas=[0])
               (_, self.bjd, _), (self.berv_start, self.berv, self.berv_end) = brv_we14py.bjdbrv(jd_utc=jd_utcs, ra=ra, dec=de, obsname=obsname, pmra=targ.pmra, pmdec=targ.pmde, parallax=0., rv=0., zmeas=[0], **obsloc)
            elif self.brvref == 'WEhtml':
               self.bjd = brv_we14html.utc2bjd(jd_utc=jd_utc, ra=ra, dec=de)
               #self.berv = brv_we14html.bvc(jd_utc=jd_utc, ra="%s+%s+%s"%targ.ra, dec="%s+%s+%s"%targ.de, obsname='ca', pmra=targ.pmra, pmdec=targ.pmde, parallax=0., rv=0., zmeas=[0], raunits='hours', deunits='degrees')[0]
               self.berv_start, self.berv, self.berv_end = brv_we14html.bvc(jd_utc=jd_utcs, ra="%s+%s+%s"%targ.ra, dec="%s+%s+%s"%targ.de, obsname='ca', pmra=targ.pmra, pmdec=targ.pmde, parallax=0., rv=0., zmeas=[0], raunits='hours', deunits='degrees')
            else:
               self.bjd, self.berv = brv_we14idl.bjdbrv(jd_utc=jd_utc[0], ra=ra, dec=de, obsname=obsname, pmra=targ.pmra, pmdec=targ.pmde, parallax=0., rv=0., zmeas=[0])

            self.berv /= 1000.   # m/s to km/s
         if self.brvref == 'DRS':
            self.bjd, self.berv = self.drsbjd, self.drsberv
            self.brvref = self.drsname
      else:
         self.bjd, self.berv = self.drsbjd, self.drsberv
         self.brvref = self.drsname

      if self.fib == 'B':
         self.berv = np.nan
         self.drsberv = np.nan

      self.header['HIERARCH SERVAL BREF'] = (self.brvref, 'Barycentric code')
      self.header['HIERARCH SERVAL BJD'] = (self.bjd, 'Barycentric Julian Day')
      self.header['HIERARCH SERVAL BERV'] = (self.berv, '[km/s] Barycentric correction')
      #self.header['HIERARCH SERVAL RA'] = (targ.ra[0], 'Barycentric code')
      #self.header['HIERARCH SERVAL DE'] = (targ.ra[0], 'Barycentric code')
      #self.header['HIERARCH SERVAL PMRA'] = (targ.ra[0], 'Barycentric code')
      #self.header['HIERARCH SERVAL PMDE'] = (targ.ra[0], 'Barycentric code')

      if self.utc:
         date = self.utc.year, self.utc.month, self.utc.day
         sunris = sunrise.sun(*date, lon=self.obs.lon, lat=self.obs.lat)
         sunset = sunrise.sun(*date, lon=self.obs.lon, lat=self.obs.lat, rise=False)
         ut = self.utc.hour + self.utc.minute/60. +  self.utc.second/3600.
         utend = ut + self.exptime/3600.
         dark = ((sunset < ut <= utend < sunris) or   # |****S__UT__R***|
                 (sunris < sunset < ut <= utend) or   # |__R*******S__UT|
                 (utend < sunris < sunset < ut)  or   # |T__R********S_U|
                 (ut < utend < sunris < sunset))      # |UT__R********S_|
         #if not dark:
            #self.flag |= sflag.daytime

      if orders is not None:
         self.read_data(orders=orders, wlog=wlog)
#         self.data(orders=orders, wlog=wlog)

   def __get_item__(self, order):
      # spectrum needs to be dict like
      return self.get_data(order)

   def get_data(self, orders=np.s_[:], wlog=def_wlog, verb=False, **kwargs):
      """Returns only data."""
      o = orders
      if self.w:
         w, f, e, b = self.w[o], self.f[o], self.e[o], self.bpmap[o]
      else:
#         w, f, e, b = read_spec(self, self.filename, inst=self.inst, orders=orders, **kwargs)
         w, f, e, b = self.data(self, orders=orders, **kwargs)
         w = np.log(w) if wlog else w.astype(np.float)
         f = f.astype(float)
         e = e.astype(float)
         self.bflag |= np.bitwise_or.reduce(b.ravel())
      return type('specdata', (object,),
               dict(w=w, f=f, e=e, bpmap=b, berv=self.berv, o=o))

   def read_data(self, verb=False, **kwargs):
      """Read only data, no header, store it as an attribute."""
      data = self.get_data(**kwargs)
      if isinstance(kwargs.get('orders'), int):
         self.wo, self.fo, self.eo, self.bo = data.w, data.f, data.e, data.bpmap
      else:
         self.w, self.f, self.e, self.bpmap = data.w, data.f, data.e, data.bpmap


class Inst:
   def __init__(self, inst):
      pass

def read_spec(self, s, inst, plot=False, **kwargs):
   #print s, inst
   sp = inst.read(self, s, **kwargs)
   return sp

   if '.tar' in s: s = file_from_tar(s, inst=inst, fib=self.fib, **kwargs)
   if 'HARP' in inst:  sp = read_harps(self, s, inst=inst, **kwargs)
   elif inst == 'CARM_VIS':  sp = read_carm_vis(self, s, **kwargs)
   elif inst == 'CARM_NIR':  sp = read_carm_nir(self, s, **kwargs)
   elif inst == 'FEROS': sp = read_feros(self, s, **kwargs)
   elif inst == 'FTS': sp = read_fts(self, s, **kwargs)
   elif inst == 'HPF': sp = read_hpf(self, s, **kwargs)
   else:
      return None
   #if hasattr(s, 'close'):
      #s.close()

   if plot:
      gplot.xlabel("'wavelength'").ylabel("'intensity'")
      for o in range(len(sp.w)):
         gplot(sp.w[o], sp.f[o], " w lp t 'order %i'"%o)
         pause(o)
   return sp

def write_template(filename, flux, wave, header=None, hdrref=None, clobber=False):
   if not header and hdrref: header = pyfits.getheader(hdrref)
   hdu = pyfits.PrimaryHDU(header=header)
   warnings.resetwarnings() # supress nasty overwrite warning http://pythonhosted.org/pyfits/users_guide/users_misc.html
   warnings.filterwarnings('ignore', category=UserWarning, append=True)
   hdu.writeto(filename, clobber=clobber, output_verify='fix')
   warnings.resetwarnings()
   warnings.filterwarnings('always', category=UserWarning, append=True)

   if isinstance(flux, np.ndarray):
      pyfits.append(filename, flux)
      pyfits.append(filename, wave)
   else:
      # pad arrays with zero to common size
      maxpix = max(arr.size for arr in flux if isinstance(arr, np.ndarray))
      flux_new = np.zeros((len(flux), maxpix))
      wave_new = np.zeros((len(flux), maxpix))
      for o,arr in enumerate(flux):
          if isinstance(arr, np.ndarray): flux_new[o,:len(arr)] = arr
      for o,arr in enumerate(wave):
          if isinstance(arr, np.ndarray): wave_new[o,:len(arr)] = arr
      pyfits.append(filename, flux_new)
      pyfits.append(filename, wave_new)

   pyfits.setval(filename, 'EXTNAME', value='SPEC', ext=1)
   pyfits.setval(filename, 'EXTNAME', value='WAVE', ext=2)
   #fitsio.write(filename, flux)

def write_res(filename, datas, extnames, header='', hdrref=None, clobber=False):
   if not header and hdrref: header = pyfits.getheader(hdrref)
   hdu = pyfits.PrimaryHDU(header=header)
   warnings.resetwarnings() # supress nasty overwrite warning http://pythonhosted.org/pyfits/users_guide/users_misc.html
   warnings.filterwarnings('ignore', category=UserWarning, append=True)
   hdu.writeto(filename, clobber=clobber, output_verify='fix')
   warnings.resetwarnings()
   warnings.filterwarnings('always', category=UserWarning, append=True)

   for i,extname in enumerate(extnames):
     data = datas[extname]
     if isinstance(data, np.ndarray):
        pyfits.append(filename, data)
     else:
        1/0

     pyfits.setval(filename, 'EXTNAME', value=extname, ext=i+1)
   #fitsio.write(filename, flux)

def write_fits(filename, data, header='', hdrref=None, clobber=True):
   if not header and hdrref: header = pyfits.getheader(hdrref)
   warnings.resetwarnings() # supress nasty overwrite warning http://pythonhosted.org/pyfits/users_guide/users_misc.html
   warnings.filterwarnings('ignore', category=UserWarning, append=True)
   pyfits.writeto(filename, data, header, clobber=clobber, output_verify='fix')
   warnings.resetwarnings()
   warnings.filterwarnings('always', category=UserWarning, append=True)

def read_template(filename):
   hdu = pyfits.open(filename)
   #return hdu[1].data, hdu[0].data  # wave, flux
   return hdu[2].data, hdu[1].data, hdu[0].header  # wave, flux

def read_harps_ccf(s):
   ccf = namedtuple('ccf', 'rvc err_rvc bis fwhm contrast mask')
   tar = None
   if ".tar" in s:
      tar = tarfile.open(s)
      extr = None
      for member in tar.getmembers():
         if 'A.fits' in member.name:
            if '_ccf_' in member.name and not extr: extr = member
            if '_bis_' in member.name: extr = member   # prefer bis
      if not extr: return ccf(0,0,0,0,0,0)
      s = tar.extractfile(extr)
   else:
      s = glob.glob(s.replace("_e2ds","_bis_*")) + glob.glob(s.replace("_e2ds","_ccf_*"))
      if s: s = s[0]   # prefer bis
      else: return ccf(*[np.nan]*6)
      #else: return ccf(0,0,0,0,0,0)
   # ccf = namedtuple('ccf', 'rvc err_rvc bis fwhm contrast mask header')
   HIERARCH = 'HIERARCH '

   if 1:
      hdr = imhead(s, HIERARCH+'ESO DRS CCF RVC', HIERARCH+'ESO DRS CCF CONTRAST', HIERARCH+'ESO DRS CCF FWHM', HIERARCH+'ESO DRS CCF MASK', HIERARCH+'ESO DRS DVRMS',HIERARCH+'ESO DRS BIS SPAN')
   elif 0:
      if ".tar" in s:
         s = tar.extractfile(extr)
         hdulist = pyfits.open(s)
         hdr = hdulist[0].header
         tar.close()
         #hdr = pyfits.getheader(s) # doesn't work for file like object?
   else:
      tar.extract(extr, path='tarfits')
      os.system('mv tarfits/* tmp.fits ')
      data,hdr = fitsio.read('tmp.fits',header=1)
      HIERARCH = ''

   if tar:
      tar.close()

   rvc = hdr[HIERARCH+'ESO DRS CCF RVC']   # [km/s]
   contrast = hdr.get(HIERARCH+'ESO DRS CCF CONTRAST', np.nan)
   fwhm = hdr[HIERARCH+'ESO DRS CCF FWHM']
   mask = hdr[HIERARCH+'ESO DRS CCF MASK']
   e_rvc = hdr.get(HIERARCH+'ESO DRS DVRMS', np.nan) / 1000.   # [km/s]
   bis = hdr.get(HIERARCH+'ESO DRS BIS SPAN', np.nan)
   return ccf(rvc, e_rvc, bis, fwhm, contrast, mask)


def read_csfs_vis(self, s, orders=None, pfits=True, verb=True):
   """
   SYNTAX: read_carm_vis(filename)
   OUTPUT: namedtuple('spectrum', 'w f berv bjd blaze drift timeid sn55 ')
           w    - wavelength
           f    - flux
           berv - Barycentric Earth Radial Velocity
           bjd  - Barycentric Julian Day
           blaze - Blaze filename
           drift - Used RV Drift
           sn55  - S_N order center55
   """
   HIERARCH = 'HIERARCH '
   hdulist = pyfits.open(s) # slow 30 ms
   if orders is None:
      hdr = hdulist[0].header
      self.header = hdr
      self.inst = hdr['INSTRUME'][0:4]+'_VIS'
   #data,hdr = fitsio.read(s,header=True)

      self.drsberv = -hdr.get('FIBARV',0)/1000.
      self.drsberv = -hdr.get(HIERARCH+'CAHA SIMU RVA',0)/1000.
      self.drsberv = hdr.get('E_BERV',0)  # @Ansgar simulation
      #self.drsbjd = hdr.get('MJD-OBS',0)
      self.drsberv = 0
      self.drsbjd = hdr.get('OBS_DAYS',0)
      self.drsbjd = hdr.get('E_BJD',0)

      self.sn55 = hdr.get(HIERARCH+'CARACAL FOX SNR 36', np.nan)
      self.blaze = '' #hdr[HIERARCH+'ESO DRS BLAZE FILE']
      self.drift = hdr.get(HIERARCH+'CARACAL DRIFT RV', np.nan)
      self.fox = HIERARCH+'CARACAL FOX XWD' in hdr

   #fileid = str(hdr.ascardlist()['MJD-OBS'])     # read the comment
      self.fileid = hdr.get('FILENAME',0) #fileid[fileid.index('(')+1:fileid.index(')')]
      self.calmode = hdr.get('SOURCE',0) #.split("_")[3] #fileid[fileid.index('(')+1:fileid.index(')')]
   #calmodedict = {'objcal':'OBJ,CAL','objsky':'OBJ,SKY'}
   #if calmode in calmodedict: calmode = calmodedict[calmode]
      self.timeid = self.fileid
      self.exptime = hdr['EXPTIME']

   if orders is not None:  # read order data
      f = hdulist['SPEC'].section[orders]
      bpmap = np.isnan(f).astype(int)            # flag 1 for nan
      w =  hdulist['WAVE'].section[orders]
      e = hdulist['SIG'].section[orders]
      if self.fox:
         e *= 100000.
         f *= 100000.
      else:
         e = 1000+0*f
         bpmap[self.f>300000] |= flag.sat
         # flag neighbours
         bpmap[:,1:] |= flag.sat * (f>300000)[:,:-1]
         bpmap[:,:-1] |= flag.sat * (f>300000)[:,1:]
      bpmap[f < -3*e] |= flag.neg
      bpmap[e==0] |= flag.nan
      return w, f, e, bpmap

   if verb: print "read_carm_vis:", self.timeid, self.header['OBJECT'], self.drsbjd, self.sn55, self.drsberv, self.drift, self.flag, self.calmode


def read_feros(self, s, orders=None, pfits=True, verb=True):
   """
   SYNTAX: read_feros(filename)
   OUTPUT: namedtuple('spectrum', 'w f berv bjd blaze drift timeid sn55 ')
           w    - wavelength
           f    - flux
           berv - Barycentric Earth Radial Velocity
           bjd  - Barycentric Julian Day
           blaze - Blaze filename
           drift - Used RV Drift
           sn55  - S_N order center55

   """
   hdulist = pyfits.open(s) # slow 30 ms
   if orders is None or self.header is None:
      HIERARCH = 'HIERARCH '
      hdr = hdulist[0].header
      self.inst = hdr['INSTRUME']

      #data,hdr = fitsio.read(s,header=True)
      indberv = [i for i, s in enumerate(hdr.values()) if 'BARY_CORR' in str(s)][0] + 1
      self.drsberv = float(hdr[indberv])
      # FEROS drsberv are wrong and already applied to the spectra
      # remove it from the spectrum
      self.drsbjd = hdr.get('MJD-OBS',np.nan) #hdr.get(HIERARCH+'ESO DRS BJD',0)
      if self.drsbjd: self.drsbjd += 2400000.5
      if self.fib=='B': self.drsberv = np.nan

      self.tmmean = 0.5 # hdr['HIERARCH ESO INS DET1 TMMEAN'] not available
      self.exptime = hdr['EXPTIME']
      self.dateobs = hdr['ARCFILE'][6:-5] # hdr['DATE-OBS']
      # FEROS has no S/N estimates; judge with airmass and seeing
      #print hdr['DATASUM'], self.exptime, hdr['HIERARCH ESO TEL AIRM START'], hdr['HIERARCH ESO TEL AMBI FWHM START']
      seefhwm = hdr['HIERARCH ESO TEL AMBI FWHM START']  # if -1 assume 3.0
      self.sn55 = 100 / hdr['HIERARCH ESO TEL AIRM START'] / (seefhwm if seefhwm>0 else 3.0)
      self.blaze = '' #hdr[HIERARCH+'ESO DRS BLAZE FILE']
      self.drift = hdr.get(HIERARCH+'ESO DRS DRIFT RV USED', np.nan)

      #fileid = str(hdr.ascardlist()['MJD-OBS'])     # read the comment
      fileid = hdr.get('ARCFILE',0) #fileid[fileid.index('(')+1:fileid.index(')')]
      self.calmode = hdr.get('ORIGFILE',0).split("_")[3] #fileid[fileid.index('(')+1:fileid.index(')')]
      calmodedict = {'objcal': 'OBJ,ThAr', 'objsky': 'OBJ,SKY'}
      if self.calmode in calmodedict: self.calmode = calmodedict[self.calmode]
      self.timeid = self.dateobs #fileid

      if self.calmode != 'OBJ,ThAr' and hdr.get('FILENAME') != 'rebinned1.bdf': self.flag = 1
      #  hdr['OBJECT'] = hdr.get('OBJECT','FOX')
      self.header = hdr
   hdr = self.header
   if verb: print "read_feros:", self.timeid, self.header['OBJECT'], self.drsbjd, self.sn55, self.drsberv, self.drift, self.flag, self.calmode

   if orders is not None:  # read order data
      if self.filename.endswith('.mt'):
         realname = os.path.realpath(self.filename)
         data = [pyfits.getdata(realname.replace('01.mt','%02i.mt'%(o+1))) for o in range(39)]
         fsize = max([len(fo) for fo in data])
         f = np.zeros((39,fsize)).astype(float)
         for o,fo in enumerate(data): f[o,0:len(fo)] = fo* 100000.
      else:
         f = hdulist[0].data * 100000.
      bpmap = np.isnan(f).astype(int)  # flag 1 for nan
      bpmap[f < 0.0001] |= flag.neg    # flag 2 for zero and negative flux

      # print " applying wavelength solution ", file
      CRPIX1 = -49.
      CDELT1  = 0.03
      w = 0 * f.astype(float)
      his = list(hdr.get_history())
      wind = [i for i,x in enumerate(his) if 'WSTART' in x][0]
      wstart = ' '.join(his[wind+1:wind+14])
      wstart = np.fromstring(wstart, dtype=float, sep=' ')
      for i in range(39):
         #wstart = 3.527250000000000E+03
         w[i] = wstart[i] + CDELT1*np.arange(w[0].size)
      c = 299792.4580  # [km/s]
      w *= (1 - self.drsberv/c)
      w = airtovac(w)
         #from gplot import  *; gplot(lam[i],data[i])
      #pause()
         #lam = hdulist['WAVE'].data
      e = np.ones_like(f) * 100.
      e[bpmap==0] = 1.0*10. #np.sqrt(data[bpmap==0]) # slow 50 ms

      if self.fib!='B':  # filter for cosmics
         look = (None,)
         kap = 4.5
         for o in range(39):
            idx, = np.where(bpmap[o]==0)
            if 1:
               # using a robust 4.5 68 percentile clipping
               #hh = np.argsort(f[o][idx])
               #ii = hh[len(hh)*0.98:]  # reject 2%
               p1sig = (100-68.2) / 2
               p16, p50, p84 = np.percentile(f[o][idx],(p1sig,50,100-p1sig))
               rbsig = (p84-p16) / 2 # robust five sigma
               ii = idx[f[o][idx] > p50+kap*rbsig]
               if o in look:
                  gplot(w[o],f[o],',',w[o][idx],f[o][idx],',',w[o][ii],f[o][ii], ",%f, %f, %f, %f"%(p16, p50, p84, p50+kap*rbsig))
            if 0:
               # moving robust 4.5sigma clipping
               csum = np.cumsum(f[o][idx])
               mvmean = (csum[400:] - csum[:-400]) / 400
               #mvmean = np.lib.pad(mvmean, (400,400), 'edge')
               mvmean = np.concatenate((np.zeros(200) + mvmean[0], mvmean,np.zeros(200) + mvmean[-1]))
               csum = np.cumsum(abs(f[o][idx]-mvmean))
               mvrbsig = (csum[400:] - csum[:-400]) / 400
               mvrbsig = np.concatenate((np.zeros(200) + mvrbsig[0], mvrbsig,np.zeros(200) + mvrbsig[-1]))
               ii = idx[f[o][idx] > mvmean+kap*mvrbsig]
               if o in look:
                  gplot(w[o],f[o],',',w[o][idx],f[o][idx],mvmean,mvmean+kap*mvrbsig,',"" us 1:3, "" us 1:4,',w[o][ii],sf[o][ii])
            bpmap[o][ii] |= flag_cosm
            if o in look:
               pause(o)
      hdulist.close()
      return w, f, e, bpmap
   hdulist.close()


def read_fts(self,s, orders=None, filename=None, pfits=True, verb=True):
   """
   SYNTAX: read_fts(filename)
   OUTPUT: namedtuple('spectrum', 'w f berv bjd blaze drift timeid sn55 ')
           w    - wavelength
           f    - flux
           berv - Barycentric Earth Radial Velocity
           bjd  - Barycentric Julian Day
           blaze - Blaze filename
           drift - Used RV Drift
           sn55  - S_N order center55

   """
   HIERARCH = 'HIERARCH '
   if orders is None:
      hdr = {'OBJECT': 'Iod'}
      self.header = hdr
      self.inst = 'FTS'
      self.drsberv = hdr.get('bla', 0)
      self.fileid = os.path.basename(s) #fileid[fileid.index('(')+1:fileid.index(')')]
      self.drsbjd = 0.0 if '.fits' in s else float(self.fileid.split('.')[1].split('_')[0])
      with open('/home/data1/fts/Lemke/2015_I/2015-01-29/DPT/parameter_Q_Si_I2_001.txt') as finfo:
         for line in finfo:
            if self.fileid.replace('_ScSm.txt','\t') in line:
               line = line.split(); date=line[2].split('/'); time=line[3].split(':')
               #from subprocess import call
               #call(['bash','date2jd', date[2], date[1], date[0]] +time)
               from subprocess import Popen, PIPE
               p = Popen(['bash','date2jd', date[2], date[1], date[0]] +time, stdin=PIPE, stdout=PIPE, stderr=PIPE)
               output, err = p.communicate()
               rc = p.returncode
               self.drsbjd = float(output)
      self.sn55 = 10
      self.blaze = '' #hdr[HIERARCH+'ESO DRS BLAZE FILE']
      self.drift = hdr.get(HIERARCH+'ESO DRS DRIFT RV USED', np.nan)
      self.calmode = hdr.get('SOURCE', 0) #.split("_")[3] #fileid[fileid.index('(')+1:fileid.index(')')]
      self.timeid = self.fileid
      self.exptime = 0
   if verb: print "read_fts:", self.timeid, self.header['OBJECT'], self.drsbjd, self.sn55, self.drsberv, self.drift, self.flag, self.calmode

   if orders is not None:  # read order data
      nord = 70    # some arbitary shaping to 70x10000
      nw = 700000
      if '.fits' in s:
         hdulist = pyfits.open(s)
         w, f = hdulist[0].data[:,1600000:]
      else:
         #w, f = np.loadtxt(s, skiprows=1600000, unpack=True) ; too slow 45s vs 5.2s
         data = np.fromfile(s, sep=' ', count=2*(1600000+nw))[2*1600000:]
         w = 1e7 / data[::2]
         f = data[1::2]

      w = 10 * w[:nw].reshape(nord,nw/nord)
      f = f[:nw].reshape(nord,nw/nord)*4000
      bpmap = np.isnan(f).astype(int)            # flag 1 for nan

      xmax = w.size
      e = np.ones_like(w)
      return w, f, e, bpmap


tarmode = 5
# 0  - extract physically (works for all, slow)
#      tarfits directory not cleaned
# 1  - treat tarfile as normal file which has offset, seek() and open()
#      but no size limit function, therefore not supported by pyfits
# 2  - as 1, but with try to overload build-in open (not working for both)
# 3  - tar.extractfile (use its build-in functions for read, offset and size, slower read!?) not supported by pyfits
# 5  - use tar.extractfile, works with pyfits, doesn't work with myfits (np.fromfile accepts only real files and files object, not tar)
# 6  - open as normal file, does not work

def file_from_tar(s, inst='HARPS', fib=None, **kwargs):
   """
   Returns a file-like object.
   Reading header and data should use the same fits reader

   >>> s = '/home/astro115/carmenes/data/HARPS/DRS/gj876/ADP.2014-12-07T19:00:30.953.tar'
   >>> tar = tarfile.open(s)
   >>> extr = tar.getmember('data/reduced/2014-12-06/HARPS.2014-12-07T00:35:29.894_e2ds_A.fits')

   """
   pat = {'HARPS': {'A': '_e2ds_A.fits', 'B': '_e2ds_B.fits'}[fib],
          'FEROS': {'A': '.1061.fits', 'B': '.1062.fits'}[fib]} [inst]
   tar = tarfile.open(s)
   for member in tar.getmembers():
       if pat in member.name: extr = member

   if tarmode in (1,2,3) and kwargs.get('pfits') == 2:
      # We could use tar.extractfile(extr) but this requires reopen the tar file
      # and does not support "with open()".
      # Instead we will use offset, seek() and open().
      # but both seems to be slower than physical extraction
      extr.mother = s
      s = extr
   elif tarmode in (0,5) and kwargs.get('pfits') == 2:
      # Open the tar file as a normal file and store the position and size of the fits file
      # as attributes.
      s = type('tarobj', (file,), {})(s, mode='rb')   # since class, because the in the buildin file class we cannot set new attributes
      s.mem = extr.name
      s.offset_data = extr.offset_data
      s.size = extr.size
      tar.close()
   elif tarmode == 5:
      # does not work with np.fromfile
      s = tar.extractfile(extr)
   else:
      print 'extract'
      tar.extract(extr, path='tarfits')   # extract physically
      s = 'tarfits/'+extr.name
   #tar.close()
   return s


class imhead(dict):
   """
   Returns fitsheader as a dict.
   
   imhead runs faster than pyfits. It does less checks, but might be
   more error prone.

   Keyword arguments:
   filename -- filename of the fitsfile

   Returns
   -------
   imhead: dict of values and dict of comments
   EXTHDRSZ: size of the header [bytes]

   Examples
   --------
   >>> x = imhead(filename)
   >>> x['OBJECT']
   >>> x.comments['OBJECT']

   """
   def __init__(self, s, *args, **kwargs):
      hdr = {}
      self.comments = {}
      extpos = kwargs.get('extpos', 0)  # position of the extension with the fitsfile
      count = kwargs.get('count', -1)
      #if not len(args):
         #args = ''   # read all
         #stop( '\n\nimhead: there must be are args; reading all keys not yet supported\n')
      args += ('NAXIS', 'BITPIX')
      NR = 0

      if isinstance(s, tarfile.TarInfo):
         if tarmode==1:
            fi = open(s.mother)
         if tarmode==3:
            fi = tarfile.open(s.mother).extractfile(s)
      elif isinstance(s, (tarfile.ExFileObject, file)):   # tarmode == 5,6
         fi = s
      elif s.endswith('.gz'):   # normal file
         fi = gzip.open(s)
      else:   # normal file
         fi = open(s)

      if not extpos and hasattr(s, 'offset_data'):
         extpos = s.offset_data
      #pause()

      #with open(s) as fi:
      if 1:
         fi.seek(extpos)
         for card in iter(lambda:fi.read(80), ''):   # read in 80 byte blocks
            NR += 1
            if card.startswith('END '): break
            if card.startswith(args):
               #key, val, comment = card.replace("= ","/ ",1).split("/ ")
               key, val, comment = card.replace(' /','= ',1).split('= ')   # does not parse HARPN.2012-09-03T05-29-56.622 "HIERARCH TNG OBS TARG NAME = 'M1'/ no comment"
               # key, val, comment = card.replace('/','= ',1).split('= ')
               hdr[key.strip()] = val.strip("' ") if "'" in val else float(val) if '.' in val else int(val)
               #hdr[key.strip()] = val.strip("' ") if any(i in val for i in "'TF") else float(val) if '.' in val and not 'NaN.' in val else int(val) if val.strip(" -").isdigit() else val
               self.comments[key.strip()] = comment
               count -= 1
               if count==0: args = () # all found; do not check cards anymore; only read to end
         #NR = (fi.tell()-extpos) / 80

      hsz = 2880 * ((NR-1)/36 + 1)
      dsz = 0     # EXTDATSZ
      self.NAXIS = hdr.get('NAXIS', 0)
      if self.NAXIS:
         self.BITPIX = hdr['BITPIX']
         self.NAXIS1 = hdr['NAXIS1']
         dsz = abs(self.BITPIX)   # data size
         dsz *= self.NAXIS1
         if self.NAXIS > 1:
            self.NAXIS2 = hdr['NAXIS2']
            dsz *= self.NAXIS2
         dsz = ((dsz/8-1)/2880+1) * 2880

      self.EXTHDRSZ = hsz   # add hdr size
      self.EXTDATA = extpos + hsz
      self.EXTEND = extpos + hsz + dsz
      super(imhead, self).__init__(hdr)


class getext(dict):
   """
   Fast reading of single orders.

   Uses imhead to scan the extension headers and to get
   the data types and sizes.

   Examples
   --------
   >>> hdu = getext(filename)
   >>> hdu['SPEC']
   >>> hdu.getdata('SPEC', o=13)

   """
   def __init__(self, s):
      """
      Extracts from header for each extension the data position, type, and shape
      and saves the extension information as dictionary.

      """
      self.fileobj = s
      ext = {}
      filepos = 0
      fileend = -1
      i = 0
      if isinstance(s, tarfile.TarInfo) and s.tarmode:
         self.fileobj = s.mother
      if hasattr(s, 'offset_data'):
         filepos = s.offset_data
         fileend = s.offset_data + s.size
      #pause()

      while True:
         exthdr = imhead(self.fileobj, 'EXTNAME', extpos=filepos)
         if exthdr.EXTEND <= filepos: break   # no new records
         filepos = exthdr.EXTEND
         ext[i] = ext[exthdr.get('EXTNAME', i)] = exthdr
         if filepos == fileend: break   # end of filemember in tarfile
         i += 1
      super(getext, self).__init__(ext)

   def getdata(self, extname=None, o=np.s_[:]):
      if extname is None:  # search first extension with data
         extname = 0
         while not self[extname].NAXIS: extname += 1
      ext = self[extname]
      dtype = {-32: '>f4', -64: '>f8'}[ext.BITPIX]
      dsize = {-32: 4, -64: 8}[ext.BITPIX]
      was_open = True
      if isinstance(self.fileobj, (tarfile.ExFileObject, file)):
         funit = self.fileobj
      else:
         funit = open(self.fileobj)
         was_open = False

#      with open(self.fileobj) as funit:
      if 1:
         if o == np.s_[:]: # read all
            funit.seek(ext.EXTDATA)
            data = np.fromfile(funit, dtype=dtype, count=ext.NAXIS1*ext.NAXIS2).reshape((ext.NAXIS2,ext.NAXIS1))
         else:
            funit.seek(ext.EXTDATA+o*ext.NAXIS1*dsize)
            data = np.fromfile(funit, dtype=dtype, count=ext.NAXIS1)

      if not was_open:
         funit.close()
      return data


def bary(obj,bjd, exptime):
   from subprocess import Popen, PIPE, STDOUT
   #bjd = 2450000.5+ obj['MJD-OBS']
   #bjd = obj['HIERARCH ESO OBS START']
   #obj = obj['OBJECT']
   #pause(bjd)
   YY, MM, DD, hh, mm, ss = bjd.replace('T', ' ').replace(':', ' ').replace('-', ' ').split(' ')
   # check alias names
   with open('./src/bary/star_alias.txt','r') as f:
      for line in f:
         if obj in line:   obj=line.split()[0]
   # prepare input file
   s = ["Select observatory (LASILLA / PARANAL):",
        "LASILLA",
        "number of observations:",
        "1",
        "coord. (0) or object(1)?",
        "1",
        "Objectname in list:",
        obj,
        "Epoch:",
        "2000.0",
        "R.A. (hh mm ss)",
        "00 00 00",
        "Dec. (deg mm ss)",
        "00 00 00",
        "Date (dd mm yyyy)",
        DD+" "+MM+" "+YY, #"13 08 2000",
        "Time (hh mm ss)",
        hh+" "+mm+" "+ss, #"07 35 49",
        "Exptime (sec)",
        str(int(exptime))]
   #pause(exptime)
   p = Popen(['./src/bary/eph'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
   eph = p.communicate(input="\n".join(s)+"\n")[0]
   if  'Star not in starlist' in eph: stop(obj, 'Star not in starlist')
   berv = float(eph.split("\n")[-3].lstrip(" DELTAV=").rstrip("m/s"))
   return berv

def airtovac(wave_air):
   """
taken from idl astrolib
;+
; NAME:
;       AIRTOVAC
; PURPOSE:
;       Convert air wavelengths to vacuum wavelengths 
; EXPLANATION:
;       Wavelengths are corrected for the index of refraction of air under 
;       standard conditions.  Wavelength values below 2000 A will not be 
;       altered.  Uses relation of Ciddor (1996).
;
; CALLING SEQUENCE:
;       AIRTOVAC, WAVE_AIR, [ WAVE_VAC]
;
; INPUT/OUTPUT:
;       WAVE_AIR - Wavelength in Angstroms, scalar or vector
;               If this is the only parameter supplied, it will be updated on
;               output to contain double precision vacuum wavelength(s). 
; OPTIONAL OUTPUT:
;        WAVE_VAC - Vacuum wavelength in Angstroms, same number of elements as
;                 WAVE_AIR, double precision
;
; EXAMPLE:
;       If the air wavelength is  W = 6056.125 (a Krypton line), then 
;       AIRTOVAC, W yields an vacuum wavelength of W = 6057.8019
;
; METHOD:
;	Formula from Ciddor 1996, Applied Optics 62, 958
;
; NOTES:
;       Take care within 1 A of 2000 A.   Wavelengths below 2000 A *in air* are
;       not altered.
; REVISION HISTORY
;       Written W. Landsman                November 1991
;       Use Ciddor (1996) formula for better accuracy in the infrared 
;           Added optional output vector, W Landsman Mar 2011
;       Iterate for better precision W.L./D. Schlegel  Mar 2011
;-
   """

   wave_vac = wave_air * 1.0
   g = wave_vac > 2000     #Only modify above 2000 A

   if np.sum(g):
      for iter in [0, 1]:
         if isinstance(g, np.ndarray):
            sigma2 = (1e4/wave_vac[g])**2.     #Convert to wavenumber squared
            # Compute conversion factor
            fact = 1. + 5.792105e-2 / (238.0185 - sigma2) + \
                               1.67917e-3 / (57.362 - sigma2)
            wave_vac[g] = wave_air[g] * fact              #Convert Wavelength
         else: # scalar version
            sigma2 = (1e4/wave_vac)**2.     #Convert to wavenumber squared
            # Compute conversion factor
            fact = 1. + 5.792105e-2 / (238.0185 - sigma2) + \
                               1.67917e-3 / (57.362 - sigma2)
            wave_vac = wave_air * fact              #Convert Wavelength

   return wave_vac


if __name__ == "__main__":
   if not 'debug' in sys.argv:
      x=Spectrum(*sys.argv[1:], inst='HARPS', pfits=2)
      x.read_data()
   else:
      sys.argv.remove('debug')
      try:
         read_spec(*sys.argv[1:])
      except:
         import pdb, sys
         e, m, tb = sys.exc_info()
         pdb.post_mortem(tb)