"""
Manipulating the Melting Curve Thermometry logs.

Log format:
[:,0] (1) Time (sec, since 1 January 1970 UTC)
[:,1] (2) Ratio transformer bridge ratio, set point
[:,2] (3) Off-balance voltage (V)
[:,3] (4) Bridge ratio (corrected for off-balance voltage)
[:,4] (5) Absolute pressure (bar, without A-transition correction)
[:,5] (6) Temperature (mK, Greywall scale)
[:,6] (7) Temperature (mK, PLTS2000 high temperature scale)
[:,7] (8) Temperature (mK, PLTS2000 low temperature scale)

07 June 2008, Lev
"""

# Changelog
# 8 Jan 2009
# use ascii2numpy.loadascii
#
# 12 Jan 2009
# add conversion r->P
# correct V_off being wrong by a factor of 100 until 19:40 on 05 Jan 2009.

import os
import os.path
import time
import datetime
import numpy
import matplotlib.dates
import flib
import timelog
import ascii2numpy
import mctscale

class MCTLog(timelog.TimeLog):
    """
    Represents a Melting Curve Thermometry log over a certain time.
    Two logs can be concatenated with a '&' sign

    'self.device' is the thermometer name, two logs must have the same gauge names in order to concatenate them.

    log format:

    [:,0] (1) Time (sec, since 1 January 1970 UTC)
    [:,1] (2) Ratio transformer bridge ratio, set point
    [:,2] (3) Lock-in Off-balance voltage (V)
    [:,3] (4) Bridge ratio (corrected for off-balance voltage)
    [:,4] (5) Absolute pressure (bar, without A-transition correction)
    [:,5] (6) Temperature (mK, Greywall scale)
    [:,6] (7) Temperature (mK, PLTS2000 high temperature scale)
    [:,7] (8) Temperature (mK, PLTS2000 low temperature scale)
    [:,8] (9) Lock-in Range (V)
    """

    @staticmethod
    def fieldcount(): "return number of columns in the self.data"; return 9

    def r_set(self): "Ratio Transformer Set Point"; return self.data[:,1] 
    def V_off(self): "Off-balance voltage [V]"; return self.data[:,2]
    def r(self): "Bridge ratio"; return self.data[:,3]
    def P(self): "Pressure[bar], no A-transition correction "; return self.data[:,4]
    def T_grwl(self): "Greywall scale temperature [mK]"; return self.data[:,5]
    def T_pltsH(self): "PLTS2000 high scale temperature [mK]"; return self.data[:,6]
    def T_pltsL(self): "PLTS2000 low scale temperature [mK]"; return self.data[:,7]
    def range(self): "Lock-in range [V]"; return self.data[:,8]

    def overload(self):
        "Return True for points where lock-in overloads. False otherwise or before 13/01/09"
        return self.V_off() / self.range() > 1.49

    def steady_r(self, before = 0, after = 2):
        if len(self) < 1:
            return self.subset([])
        
        t = self.t()
        r_set = self.r_set()
        range = self.range()
        
        mask = numpy.ones([len(self)], dtype=bool)
        
        for n in xrange(1, len(self)):
            if r_set[n] != r_set[n-1] or (numpy.isfinite(range[n]) and range[n] != range[n-1]):
                mask[(t >= t[n] - before) & (t <= t[n] + after)] = False
        
        return self.subset(mask)
        
#        return self.subset(self.steady(self.r_set(), settle_time, after)).period(min(self.t()) + settle_time, max(self.t()) - after)
    
    def __repr__(self): return "%s Log %s" % (self.device, self.datespan())
    
    devNames = {'_MCT_NuclearStage.dat': 'MCTNS', '_MCTNS.dat': 'MCTNS',
                '_MCT_MixingChamber.dat': 'MCTMC', '_MCTMC.dat': 'MCTMC'}

    @staticmethod
    def load(filename):
        """
        Load an MCT log from a given file
        The thermometer is infered from the filename suffix.
        """
        basename = os.path.basename(filename)
        suffix = basename[8:]
        
        if not MCTLog.devNames.has_key(suffix):
            return None

#        data = flib.loadascii(filename, usecols=range(MCTLog.fieldcount()))

        if suffix.count('_') == 2:
            # file in old format without lock-in range
            # filename ends with '_MCT_NuclearStage.dat' or '_MCT_MixingChamber.dat'
            # with two '_'
            # [:,0] (1) Time (sec, since 1 January 1970 UTC)
            # [:,1] (2) Ratio transformer bridge ratio, set point
            # [:,2] (3) Lock-in Off-balance voltage (V)
            # [:,3] (4) Bridge ratio (corrected for off-balance voltage)
            # [:,4] (5) Absolute pressure (bar, without A-transition correction)
            # [:,5] (6) Temperature (mK, Greywall scale)
            # [:,6] (7) Temperature (mK, PLTS2000 high temperature scale)
            # [:,7] (8) Temperature (mK, PLTS2000 low temperature scale)
            olddata = ascii2numpy.loadascii(filename, cols=8)
            data = numpy.zeros([len(olddata), 9])
            data[:,:8] = olddata[:,:]
            data[:,8] = numpy.NaN
        else:
            # file in new format with lock-in range (used from 13 Jan 2009)
            # filename ends with '_MCTNS.dat' or '_MCTMC.dat' with two '_'
            # [:,0] (1) Time (sec, since 1 January 1970 UTC)
            # [:,1] (2) Ratio transformer bridge ratio, set point
            # [:,2] (3) Lock-in Off-balance voltage (V)
            # [:,3] (4) Bridge ratio (corrected for off-balance voltage)
            # [:,4] (5) Absolute pressure (bar, without A-transition correction)
            # [:,5] (6) Temperature (mK, Greywall scale)
            # [:,6] (7) Temperature (mK, PLTS2000 high temperature scale)
            # [:,7] (8) Temperature (mK, PLTS2000 low temperature scale)
            # [:,8] (9) Lock-in Range (V)
            data = ascii2numpy.loadascii(filename, cols=MCTLog.fieldcount())
                
        # timestamps are stored as seconds since 00:00 1 Jan 1904.
        # Convert to seconds since 00:00 1 Jan 1970, C and UNIX time format.
        data[:,0] = flib.labview2tm(data[:,0])
        
        # out of range temperatures are encoded as -1. Change this to NaN
        data[data[:,5] < 0, 5] = numpy.NaN
        data[data[:,6] < 0, 6] = numpy.NaN
        data[data[:,7] < 0, 7] = numpy.NaN
        
        # before 19:40 on 05 Jan 2009 the offset voltage stored was 100 times wrong
        ii = data[:,0] < flib.tm('05 Jan 2009 19:40 UTC')
        if any(ii):
            data[ii,2] *= 0.01;
        
        return MCTLog(data, MCTLog.devNames[suffix])

    def cure_r_set(self):
        """
        Before 15:30 on 13/01/09 r_set was one step in the future
        Cure this in place by shifting the r_set column of the data.
        The first entry in the log is not cured, so it is best to apply
        this after consequent days are merged into one log.
        This function is called automatically by MCTLog.loadMCTNS().
        
        The log should be sorted chronologically before calling this function.
        """
        t = self.t()
        ii = t < flib.tm('13 Jan 2009 15:30 UTC')
        if not any(ii): return
        # first column is r_set, shift up by one raw
        self.data[ii,1][1:] = self.data[ii,1][0:-1]

    @staticmethod
    def loadMCTNS(run, start = None, end = None):
        """
        Return highP paroscientific log for a given run, withing [start, end]
        period if boundaries are specified.
        """
        mctlog = MCTLog.loaddir(flib.smbpath(r'\\phpc338\Run%d\MCT_NuclearStage' % run), start=start, end=end).period(start, end)
        mctlog.cure_r_set()
        return mctlog
#        return MCTLog.loaddir(r'C:\pub\MCTNS\run%d' % run, start=start, end=end).period(start, end)

        # MCTNS calibration from 11/02/08
    cal_MCTNS = numpy.array([-1.0626671184, 5.8370970742, -12.8906326324, -9.9311390279, 51.7090374997])

    @staticmethod
    def r2P(r, cal=numpy.array([-1.0626671184, 5.8370970742, -12.8906326324, -9.9311390279, 51.7090374997])):
        """
        Convert bridge ratio to pressure
        r - bridge ratio or an array of them
        cal - a calibration polynomial that transfers from r/(1-r) to pressure
        from a highest power of the argument a constant, like in numpy.polyval.
        Defaults to MCTNS calibration from 11/02/08.
        """
        return numpy.polyval(cal, r / (1-r))

    def r_corr(self, k=1.0): "corrected ULT MCTNS (Run 20) bridge ratio"; return self.r_set() - self.V_off() / (-0.130913*self.r() + 0.137265) * k

    def corr_r_P_Tgrwl(self, rA=0.495190, cal=numpy.array([-1.0626671184, 5.8370970742, -12.8906326324, -9.9311390279, 51.7090374997]), k_V2r = 1.0):
        """
        correct in place the bridge ratio, pressure and Greywall temperature
        NOTE: PLTS temperatures are left uncorrected
        rA - bridge ratio at superfluid transition
        cal - a calibration polynomial that transfers from r/(1-r) to pressure
        rA and cal defaults to MCTNS @ ULT on Run 20 calibration
        """
        self.data[:,3] = self.r_corr(k=k_V2r)
        self.data[:,4] = self.r2P(self.r(), cal) - self.r2P(rA, cal) + mctscale.greywall_PA()
        self.data[:,5] = mctscale.greywall_P2T(self.P() - mctscale.greywall_PA())
