#!/usr/bin/env python3
import os
import re
import struct
import datetime
import logging
from isceobj.Orbit.Orbit import Orbit
from isceobj.Orbit.Orbit import StateVector
from iscesys.DateTimeUtil.DateTimeUtil import DateTimeUtil as DTU
import isceobj.Planet.AstronomicalHandbook as AstronomicalHandbook
import isceobj.Planet.Ellipsoid as Ellipsoid
from isceobj.Util.decorators import type_check, pickled, logged


@pickled
class ODR(object):
    """A class to parse Orbital Data Records (ODR) generated by Delft"""

    logging_name  = 'isceobj.Orbit.ODR'
    
    @logged
    def __init__(self, file=None):
        self._file = file
        self._format = None
        self._satellite = None
        self._arcNumber = None
        self._cycleLength = None
        self._numberOfRecords = None
        self._version = None
        self._ephemeris = Orbit()
        self._ephemeris.configure()
        self._ephemeris.setOrbitSource('ODR')
        self._ephemeris.setReferenceFrame('ECR')
        self.grs80 = Ellipsoid.Ellipsoid(
            a=AstronomicalHandbook.PlanetsData.ellipsoid['Earth']['GRS-80'].a,
            e2=AstronomicalHandbook.PlanetsData.ellipsoid['Earth']['GRS-80'].e2
             )

        return None

    #jng added the start and stop time. The computation of the velocities seems pretty time comsuming, so limit the orbit data extraction only to startTime nad stopTime
    def parseHeader(self, startTime=None, stopTime=None):
        fp = None
        try:
            fp = open(self._file,'rb')
        except IOError as strerr:
            self.logger.error(strerr)
        buffer = fp.read(16)
        # Header 1
        (format,satellite,dataStartTimeSeconds) = struct.unpack('>4s8si',buffer)
        buffer = fp.read(16)
        # Header 2
        (cycleLength,number,numberOfRecords,version) = struct.unpack('>4i',buffer)

        self._format = format.decode('utf-8')
        self._satellite = satellite.decode('utf-8')
        self._arcNumber = number
        self._cycleLength = cycleLength*1e3 # In cycle length in days
        self._numberOfRecords = numberOfRecords
        self._version = version

        positions = []
        for i in range(numberOfRecords):
            buffer = fp.read(16)
            if not startTime == None:
                position = self.parseDataRecords(buffer)
                if position['time'] < startTime:
                    continue

            if not stopTime == None:
                position = self.parseDataRecords(buffer)
                if position['time'] > stopTime:
                    continue

            positions.append(self.parseDataRecords(buffer))

        self.createStateVectors(positions)
        fp.close()

    def parseDataRecords(self,buffer):
        """Parse the individual data records for this ODR file"""
        (timeSeconds,latitude,longitude,height) = struct.unpack('>4i',buffer)
        time = self._utcSecondsToDatetime(timeSeconds)
        if (self._format == '@ODR'):
            latitude = latitude*1e-6
            longitude = longitude*1e-6
        elif (self._format == 'xODR'):
            latitude = latitude*1e-7
            longitude = longitude*1e-7
        height = height*1e-3

        xyz = self._convertToXYZ(latitude,longitude,height)
        return ({'time': time,
                 'x':xyz[0],
                 'y':xyz[1],
                 'z':xyz[2]})


    def createStateVectors(self,positions):
        """Calculate the satellite velocity from the position data and create StateVector objects"""
        
        for i in range(len(positions)):
            t0 = positions[i]['time']
            x0 = positions[i]['x']
            y0 = positions[i]['y']
            z0 = positions[i]['z']

            sv = StateVector()
            sv.configure()
            sv.setTime(t0)
            sv.setPosition([x0,y0,z0])
            sv.setVelocity([0.0,0.0,0.0])            
            self._ephemeris.addStateVector(sv)
        self._calculateVelocities()
        
    def _calculateVelocities(self):
        ##PSA: Need enough state vectors before and after to make sure interpolation is reasonable
        ##Call to trimOrbit is always needed to get rid of padding state vectors
        for sv in self._ephemeris[5:-5]:           
            t0 = sv.getTime()
            t1 = t0  + datetime.timedelta(seconds=-0.5)
            t2 = t0  + datetime.timedelta(seconds=0.5)
            
            try:
                sv1 = self._ephemeris.interpolateOrbit(t1,method='legendre')
                sv2 = self._ephemeris.interpolateOrbit(t2,method='legendre')
            except ValueError:
                continue
            if (not sv1) or (not sv2):
                continue
            v1 = sv1.getPosition()
            v2 = sv2.getPosition()
            vx = (v2[0]-v1[0])
            vy = (v2[1]-v1[1])
            vz = (v2[2]-v1[2])       
            sv.setVelocity([vx,vy,vz])
                
    def trimOrbit(self,startTime,stopTime):
        """Trim the list of state vectors to encompass the time span [startTime:stopTime]"""
        
        newOrbit = Orbit()
        newOrbit.configure()
        newOrbit.setOrbitSource('ODR')
        newOrbit.setReferenceFrame('ECR')
        for sv in self._ephemeris:
            if ((sv.getTime() > startTime) and (sv.getTime() < stopTime)):
                newOrbit.addStateVector(sv)
                
        return newOrbit

    def getEphemeris(self):
        return self._ephemeris

    def _convertToXYZ(self,latitude,longitude,height):
        # The latitude, longitude and height are referenced to the center of mass of the satellite above the GRS80 ellipsoid
        xyz = self.grs80.llh_to_xyz([latitude,longitude,height])
        return xyz

    def _utcSecondsToDatetime(self,seconds):
        """All of the ODR records are in UTC seconds from 1 Jan. 1985"""
        dataTime = datetime.datetime(year=1985,month=1,day=1)
        dataTime = dataTime + datetime.timedelta(seconds=seconds)
        return dataTime
        
class Arclist(object):
    """A class for parsing the ODR arclist file"""

    def __init__(self,file=None):
        self.file = file
        self.arclist = []

    def parse(self):
        begin = False
        fp = open(self.file)
        for line in fp.readlines():
            if (begin):
                arc = self.parseLine(line)
                self.arclist.append(arc)
            # I should pre-compile this regex to speed-up the execution
            if (re.search('^Arc#',line)):
                begin=True

        fp.close()

    def parseLine(self,line):        
        arc = Arc()
        arc.number = line[0:3] # Arc number
        arc.start = datetime.datetime.strptime(line[5:17],'%y%m%d %H:%M') # Starting time for the arc
        arc.stop = datetime.datetime.strptime(line[20:32],'%y%m%d %H:%M') # End time for the arc
        arc.slrResidual = line[34:38] # Satellite laser ranging residual in cm        
        arc.crossOver = line[39:43]
        arc.altimeter = line[45:49]
        arc.repeat = line[51:57] # Repeat cycle in days
        arc.version = line[58:61] # Version number
        arc.precise = datetime.datetime.strptime(line[63:78],'%y%m%d %H:%M:%S') # Starting time of the precise segment of the arc

        return arc

    def getArc(self,time):
        """Given a datetime object, determine the first arc number that contains precise ephemeris"""
        inRange = []
        # Make a list containing all of the 
        # arcs that span <code>time</code>
        for arc in self.arclist:
            if (arc.inRange(time)):
                inRange.append(arc)

        # Find the arc that contains the <code>time</code>
        # in the "precise" region of the arc
        for arc in inRange:
            if (time >= arc.precise):
                return arc.number

        return None
    
    def getOrbitFile(self,time):
        number = self.getArc(time)
        return "ODR." + number
        
           
class Arc(object):
    """A class representing an orbital arc segment"""

    def __init__(self):
        self.number = None
        self._start = None
        self._stop = None
        self.slrResidual = None
        self.xover = None
        self.altim = None
        self.repeatCycle = None
        self.version = None
        self._precise = None

    def getStart(self):
        return self._start

    @type_check(datetime.datetime)
    def setStart(self,start):
        self._start = start
        pass
    
    def getStop(self):
        return self._stop

    @type_check(datetime.datetime)
    def setStop(self,stop):
        self._stop = stop
        pass

    def getPrecise(self):
        return self._precise

    @type_check(datetime.datetime)
    def setPrecise(self, precise):
        self._precise = precise
        pass

    def inRange(self,time):
        """Determine whether a time stamp lies within the start and stop times"""
        return self._start <= time <= self._stop
    
    start = property(fget=getStart,fset=setStart)
    stop = property(fget=getStop,fset=setStop)
    precise = property(fget=getPrecise,fset=setPrecise)
    pass
   



