#from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtCore import QRunnable,pyqtSlot,QThreadPool,QMetaObject,Qt,Q_ARG,QTimer,QTime,QProcess,QItemSelection,QItemSelectionModel,QSignalBlocker,QRectF,pyqtSignal
from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, QPushButton, QStackedLayout,QItemDelegate,
                             QSlider,
                             QHBoxLayout, QVBoxLayout, QGridLayout, QApplication, QAction,QAbstractItemView,
                             QTabWidget,QLineEdit,QLabel,QComboBox,QDialogButtonBox,QPlainTextEdit,QProgressBar,
                             QSizePolicy,QMessageBox,QTableWidget,QTableWidgetItem,QTextEdit,QHeaderView,QMenu)
from PyQt5.QtGui import QPalette,QColor,QTextCursor,QPainter,QFont
import csv
import numpy as np
import traceback

import sys,os,time,struct,math,itertools,json
import pyqtgraph as pg
from PC410Controller import PC410Controller

"""
class PlotDataset(object):
class PlotDataItem(GraphicsObject):

"""

class QCycleThread(QRunnable):
    # Custom threader for running cycle() in pc410controller in a thread
    def __init__(self, name, pc410controller):
        QRunnable.__init__(self)
        self.name=name
        self.pc410controller=pc410controller
    def run(self):
        try:
            self.pc410controller.running=1
            print("Starting ct " + self.name)
            self.pc410controller.cycle(self.pc410controller.tstart0)
            print("Exiting ct " + self.name)
        except:
            print(traceback.format_exc())
        finally:
            self.pc410controller.running=0
    def start(self):
        QThreadPool.globalInstance().setObjectName(self.name)
        QThreadPool.globalInstance().start(self)
    def join(self):
        pass

class QRunnableThread(QRunnable):
    def __init__(self, name, parent):
        QRunnable.__init__(self)
        self.name=name
        self.parent=parent
    def run(self):
        try:
            self.parent.running=1
            print("Starting ct " + self.name)
            self.parent.cycle(self.parent.tstart0)
            print("Exiting rt " + self.name)
        except:
            print(traceback.format_exc())
        finally:
            self.parent.running=0
    def start(self):
        QThreadPool.globalInstance().setObjectName(self.name)
        QThreadPool.globalInstance().start(self)
    def join(self):
        pass
    
class ProcessRunnable(QRunnable):
    def __init__(self, target, args=None,start=True):
        QRunnable.__init__(self)
        self.name="ProcessRunnable"
        self.t = target
        if args==None: args=()
        self.args = args
        if start: self.start()

    def run(self):
        try:
            self.t(*self.args)
        except:
            print(traceback.format_exc())
        finally:
            print("exiting",self.t)
            
    def start(self):
        QThreadPool.globalInstance().setObjectName(self.name)
        QThreadPool.globalInstance().start(self)

class TableView(QTableWidget):
    def __init__(self, headers, data, *args):
        QTableWidget.__init__(self, *args)
        self.data = data
        self.headers = headers
        self.setData()
        self.resizeColumnsToContents()
        self.resizeRowsToContents()
        self.cbSelectionChanged=None
        self.cbCellChanged=None

        self.cellChanged.connect(self.sCellChanged)

    def sCellChanged(self,row,column):
        if self.state()==QAbstractItemView.EditingState:
            txt=self.item(row,column).text()
            if self.cbCellChanged:
                self.cbCellChanged(row,column,txt)

    def selectionChanged(self,selected, deselected):
        super().selectionChanged(selected,deselected)
        indexes=selected.indexes()
        dindexes=deselected.indexes()
        rows=set([item.row() for item in indexes])
        drows=set([item.row() for item in dindexes])
        if rows:
            row0=min(rows)
            row1=max(rows)
            if self.cbSelectionChanged:
                self.cbSelectionChanged(row0,row1)

    def setData(self):
        horHeaders = []
        for n, key in enumerate(self.headers):
            horHeaders.append(key)
            for m, item in enumerate(self.data[key]):                
                newitem = QTableWidgetItem(str(item))
                self.setItem(m, n, newitem)
        self.setHorizontalHeaderLabels(horHeaders)

class ValueSelectDelegate(QItemDelegate):
    def __init__(self, parent=None,app=None):
        QItemDelegate.__init__(self, parent)
        self.app=app
    def createEditor(self, parent, option, index):
        model_value = index.model().data(index, Qt.DisplayRole)
        editor = QComboBox(parent)
        lines=[ x for x in model_value.split("\n") if x]
        editor.addItems(lines)
        return editor
    def setEditorData(self, editor, index):        
        model_value = index.model().data(index, Qt.EditRole)
        # must get value from actual data and select value matching in list        
        current_index = editor.findText(model_value)
        if current_index > 0:
            editor.setCurrentIndex(current_index)
    def setModelData(self, editor, model, index):
        editor_value = editor.currentText()
        parts=editor_value.split("=")
        if len(parts)>1:
            value=parts[0].strip()
            self.app.realTableCellChanged(index.row(),index.column(),value)
            #print("setModelData",index.row(),index.column(),editor_value,model)
            # model.setData(index, editor_value, QtCore.Qt.EditRole)

class SimplifiedPlotItemMovement(pg.PlotItem):
    pointmoved = pyqtSignal([int,float,float],name="pointMoved")

    def __init__(self):
        pg.PlotItem.__init__(self)

        # Initialize the class instance variables.
        self.dragPoint = None
        self.dragIndex = -1
        self.dragOffset = 0
        self.plot_item_control = None

    def mouseDragEvent(self, ev):
        # Check to make sure the button is the left mouse button. If not, ignore it.
        # Would have to change this if porting to Mac I assume.
        if ev.button() != Qt.MouseButton.LeftButton:
            ev.ignore()
            return

        if ev.isStart():
            # We are already one step into the drag.
            # Find the point(s) at the mouse cursor when the button was first
            # pressed:
            # pos = ev.buttonDownPos()
            pos = ev.buttonDownScenePos()
            # Switch position into local coords using viewbox
            local_pos = self.vb.mapSceneToView(pos)

            for item in self.dataItems:
                new_pts = item.scatter.pointsAt(local_pos)
                if len(new_pts) == 1:
                    # Store the drag point and the index of the point for future reference.
                    self.dragPoint = new_pts[0]
                    self.dragIndex = item.scatter.points().tolist().index(new_pts[0])
                    # Find the initial offset of the drag operation from the current point.
                    # This value should allow for the initial mismatch from cursor to point to be accounted for.

                    self.dragOffset = new_pts[0].pos() - local_pos
                    # If we get here, accept the event to prevent the screen from panning during the drag.

                    
                    ev.accept()
        elif ev.isFinish():
            # The drag is finished. Reset the drag point and index.
            self.dragPoint = None
            self.dragIndex = -1
            return
        else:
            # If we get here, this isn't the start or end. Somewhere in the middle.
            if self.dragPoint is None:
                # We aren't dragging a point so ignore the event and allow the panning to continue
                ev.ignore()
                return
            else:
                # We are dragging a point. Find the local position of the event.
                local_pos = self.vb.mapSceneToView(ev.scenePos())

                # Update the point in the PlotDataItem using get/set data.
                # If we had more than one plotdataitem we would need to search/store which item
                # is has a point being moved. For this example we know it is the plot_item_control object.
                x,y = self.plot_item_control.getData()
                # Be sure to add in the initial drag offset to each coordinate to account for the initial mismatch.
                x[self.dragIndex] = local_pos.x() + self.dragOffset.x()
                y[self.dragIndex] = local_pos.y() + self.dragOffset.y()
                # Update the PlotDataItem (this will automatically update the graphics when a change is detected)
                #self.plot_item_control.setData(x, y)
                
                pos=local_pos.x(),local_pos.y()
                self.pointmoved.emit(self.dragIndex,*pos)
                



            
class MainWindow(QMainWindow):
    def __init__(self,ctx,argv):
        super(MainWindow, self).__init__()

        self.indicatorHeight=32
        self.resultHeight=36
        self.buttonHeight=43
        self.cursorMoved=0
        self.selectedRow=0
        self.fontsize=12

        self.infoColumns=2
        self.clockHeight=70 # 100
        self.progressCompact=1
        self.buttonRows=1
        self.withActionLabel=0
        
        self.running=0
        self.starttime=time.time()
        self.lasttime=self.starttime
        self.errorMessage=""
        

        self.tfontsize=10
        self.tfont = QFont('Courier', self.tfontsize) #, QFont.Bold)
        
        self.mintemp=20.0

        self.e={}

        home=os.environ["HOME"]
        if not home: raise Exeption("HOME environment variable not set!")
        self.home=home
        user=os.environ["USER"]
        if not home: raise Exeption("USER environment variable not set!")
        self.user=user


        self.loadSettings()

        
        self.build()

        self.pc410controller=PC410Controller(QCycleThread,self.cmdlog,self.infocallback,self.statecallback,self.recordcallback)
        self.pc410controller.run()

    def loadSettings(self):
        path=os.path.join(os.environ["HOME"],".pc410Settings")
        self.settings={}
        try:
            self.settings=json.loads(open(path).read())
        except:
            print("exception reading and decoding",path)

        fieldrows=self.settings.get("fieldrows",[])
        if not fieldrows:
            fieldrows=[["Sn63Pb37",        1,85,70, 1,150,35, 1,185,50, -0.01],
                       ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
                       ]
            self.settings["fieldrows"]=fieldrows
        
        
    def saveSettings(self):
        rowPosition = self.table.rowCount()

        fieldrows=[]
        for i in range(0,rowPosition):
            name,program=self.getProgramParameters(i)
            values=list(itertools.chain(*program))
            fieldrows+=[[name]+values]
        self.settings["fieldrows"]=fieldrows
        path=os.path.join(os.environ["HOME"],".pc410Settings")
        try:
            open(path,"w").write(json.dumps(self.settings))
        except:
            print("exception writing settings",path)
            raise
        
            
            
        
        
        

    # PTN is which program
    # STEP is which step of program...
    
    def addButton(self,layout,name,fn,key=None,colour="white"):
        button = QPushButton(self)
        button.setText(name)
        hmargin=(self.buttonHeight-self.fontsize)//2-5
        button.setSizePolicy(
            QSizePolicy.Preferred,
            QSizePolicy.Expanding)
        button.setContentsMargins(20,hmargin,20,hmargin)
        #button.setStyleSheet("background-color: %s;"%colour)
        #button.setStyleSheet("padding: 20px; background-color: %s;"%colour)
        button.setStyleSheet("padding: %dpx; background-color: %s;"%(hmargin,colour))

        #button.setGeometry(200, 150, 100, 40)
        #button.setFont(QFont('Times', 15))
        
        if not key: key=name
        self.e[key]=(name,key,fn,button)
        layout.addWidget(button)
        button.clicked.connect(fn)

    def saveAction(self):
        self.saveSettings()
        
    def stopAction(self):
        self.pc410controller.cmdStop()
        self.running=0
    def runAction(self):
        self.pc410controller.cmdRun()
        self.running=1
        self.clearRunRecord()
        self.starttime=time.time()

    def runningToStopped(self):
        self.running=0
    def stoppedToRunning(self):
        self.running=1
        self.clearRunRecord()
        self.starttime=time.time()
        
    def readAction(self):
        program=self.pc410controller.readProgram()
        print(program)
        if program:
            # update current row in table
            smodel=self.table.selectionModel()
            sindexes=smodel.selectedIndexes()
            if sindexes:
                rowPosition=max(set([s.row() for s in sindexes]))
            else:
                rowPosition = self.table.rowCount()
                self.table.insertRow(rowPosition)
            print(rowPosition)
            name="read"
            self.setProgramParameters(name,program,rowPosition)
        
    def writeAction(self):
        smodel=self.table.selectionModel()
        sindexes=smodel.selectedIndexes()
        if sindexes:
            rowSet=set([s.row() for s in sindexes])
            if len(rowSet)!=1:
                # fail
                return
            rowPosition=min(rowSet)

            name,program=self.getProgramParameters(rowPosition)
            if program:
                result=self.pc410controller.writeProgram(program)
                if result!=0:
                    print("### failed to write program!!!")
        
    def clearRunRecord(self):
        self.runrecord=([],[])
        
    def addRowAction(self):
        self.addRow(-1)
    
    def addRow(self,rowPosition=-1):
        if rowPosition==-1:
            smodel=self.table.selectionModel()
            sindexes=smodel.selectedIndexes()
            if sindexes:
                rowPosition=max(set([s.row() for s in sindexes]))+1
            else:
                rowPosition = self.table.rowCount()
        self.table.insertRow(rowPosition)

    def removeRow(self,rowPosition):
        if self.table.rowCount()<=1:
            self.addRow(-1)
        self.table.removeRow(rowPosition)
        

    #@pyqtSlot(int,float,float)
    def tableContextMenu(self,point):
        index = self.table.indexAt(point)
        if index.isValid() :         
            # show context menu
            self.contextMenu = QMenu(self)
            taddrowabove = self.contextMenu.addAction("Insert row above")
            taddrowbelow = self.contextMenu.addAction("Insert row below")
            self.contextMenu.addSeparator()
            tdeleterow = self.contextMenu.addAction("Delete row")
            
            # I want to perform actions only for a single column(E.g: Context menu only for column 4 of my table
            # Need help here....???
            action = self.contextMenu.exec_(self.table.mapToGlobal(point))

            row0=index.row()
            if action == taddrowabove:
                self.addRow(row0)            
            elif action == taddrowbelow:
                self.addRow(row0+1)
            elif action == tdeleterow:
                self.removeRow(row0)

    def getTimeOfRun(self):
        dt=self.lasttime-self.starttime
    def showTime(self):
        if self.running:
            self.lasttime=time.time()
        dt=self.lasttime-self.starttime
        h=int((dt/3600)%100)
        m=int((dt/60)%60)
        s=int(dt%60)
        label_time="%02d:%02d:%02d"%(h,m,s)
        #current_time = QTime.currentTime()
        #label_time = current_time.toString('hh:mm:ss')
        self.clocklabel.setText(label_time)
         
        
    def build(self):
        if 1:
            font = QFont('Arial', self.fontsize) #, QFont.Bold)
            self.setFont(font)        


        toplayout = QHBoxLayout()            
            

        
        
        layout = QVBoxLayout()
        toplayout.setContentsMargins(0,0,0,0)
        layout.setContentsMargins(0,0,0,0)
        toplayout.addLayout(layout)


        layoutside = QVBoxLayout()
        toplayout.addLayout(layoutside)
        self.addButton(layoutside,"Save",self.saveAction,"action-save",colour="green")
        self.addButton(layoutside,"Add row",self.addRowAction,"action-addrow",colour="cyan")
        self.addButton(layoutside,"Run",self.runAction,"action-run",colour="yellow")
        self.addButton(layoutside,"Stop",self.stopAction,"action-stop",colour="red")
        self.addButton(layoutside,"Read program",self.readAction,"action-readprogram",colour="blue")
        self.addButton(layoutside,"Write program",self.writeAction,"action-writeprogram",colour="green")
            

        l=QLabel("Temperature over time")
        layout.addWidget(l)
        
        self.plot_graph = pg.GraphicsLayoutWidget()
        #self.plot_graph = pg.PlotWidget()
        self.plot_graph.setBackground("w")
        self.lineplot=SimplifiedPlotItemMovement()
        self.plot_graph.addItem(self.lineplot)
        self.lineplot.pointMoved.connect(self.graphPointMoved)
        self.plottexts=[]

        if 1:
            self.scatter = pg.ScatterPlotItem(size=2, brush=pg.mkBrush(255, 0, 0, 0))
            #plot_item = self.plot_graph.addPlot()
            #scatter_item = pg.ScatterPlotItem()
            self.lineplot.addItem(self.scatter)
            #self.plot_graph.addItem(self.scatter)
        else:
            self.recordplot = pg.PlotCurveItem(size=2, pen=pg.mkPen(color='#ff0000', width=1))
            self.plot_graph.addItem(self.recordplot)
        
        self.clearRunRecord()
        
        self.lineplot.setTitle('<span style="color: blue; font-size: 20pt">Temperature vs Time</span>')
        self.lineplot.setLabel("left", "Temperature (°C)")
        self.lineplot.setLabel("bottom", "Time (seconds)")


        self.timer = QTimer(self)
        self.timer.timeout.connect(self.showTime)
        self.timer.start(1000)
        hsize=self.clockHeight-5*2
        font = QFont('Arial', hsize, QFont.Bold)
        self.clocklabel = QLabel()
        self.clocklabel.setAlignment(Qt.AlignCenter)
        self.clocklabel.setFont(font)
        self.clocklabel.setContentsMargins(5,5,5,5)
        layoutside.addWidget(self.clocklabel)
            

        info=QGridLayout()
        infoslots={}
        def addInfoSlot(infoslots,name,label=None):
            if not label: label=name
            row=len(infoslots)
            l=QLabel(label)
            e=QLabel()                    
            infoslots[name]=(name,l,e)
            info.addWidget(l,row,0)
            info.addWidget(e,row,1)
        def addInfoSlot2D(infoslots,name,label=None):
            if not label: label=name
            count=len(infoslots)
            row=count//2
            col=count%2
            
            l=QLabel(label)
            e=QLabel()                    
            infoslots[name]=(name,l,e)
            info.addWidget(l,row,col*2+0)
            info.addWidget(e,row,col*2+1)
                
        faddinfoslot=addInfoSlot
        if self.infoColumns==2: faddinfoslot=addInfoSlot2D

        for param in ["PV","OP","SP","SL","HA","LA","DA","XP","TI","TD","HB","LB","CH","CC","RG","HS","LS","BP","HO","SR","Lc","SW","XS","OS"]:
            # "HB",
            faddinfoslot(infoslots,param)


        layoutside.addLayout(info)
        self.infoslots=infoslots
            
                
        layout.addWidget(self.plot_graph)

        if 1:

            l=QLabel("Settings")
            layout.addWidget(l)


            if 0:
                hbuttons = QHBoxLayout()            
                self.addButton(hbuttons,"Add row",self.addRow,"action-addrow",colour="cyan")
                layout.addLayout(hbuttons)
            
            """
            Lead Sn63Pb37
            step    r      L       d
            1       1      85      70
            2       1      150     35
            3       1      185     50
            4       END    Hb=230     
            
            Lead-Free Sn965.5Ag3Cu0.5
            step    r      L       d
            1       1      85      60
            2       1      140     45
            3       1      170     25
            3       1      220     50
            5       END    hb=230

            """
            
            fieldrows=[["Sn63Pb37",        1,85,70, 1,150,35, 1,185,50, -0.01],
                       ["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
                       ]
            fieldrows=self.settings.get("fieldrows",fieldrows)

            headers=["Name"]
            for i in range(1,9):
                headers+=["r%d"%i,"l%d"%i,"t%d"%i]

            data={}            
            for i in range(0,len(headers)):
                data[headers[i]]=[]
            for row in fieldrows:
                fieldrow={}
                for ki in range(0,len(headers)):
                    k=headers[ki]
                    if ki>=len(row): v=""
                    else:            v=row[ki]
                    data[k]+=[v]
                #data[self.decoded]+=[0]
                
            table = TableView(headers,data,len(data[headers[0]]),len(headers))
            self.table=table
            table.setContentsMargins(0,0,0,0)

            table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
            table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
            table.setSelectionBehavior(QAbstractItemView.SelectRows);

            table.cbSelectionChanged=self.tableSelectionChanged
            table.cbCellChanged=self.tableSelectionChanged #CellChanged

            def tableClicked(*args):
                item=args[0]
                row=item.row()
            
            table.clicked.connect(tableClicked)


            # setting context menu policy on my table, "self.table"
            self.table.setContextMenuPolicy(Qt.CustomContextMenu)

            # setting context menu request  by calling a function,"self.tableContextMenu"
            self.table.customContextMenuRequested.connect(self.tableContextMenu)

            
            table.show()
            table.resize(250, 150)
            layout.addWidget(self.table)

        if layout:
            widget = QWidget()
            widget.setLayout(toplayout)
            self.setCentralWidget(widget)

    @pyqtSlot(int,float,float)
    def graphPointMoved(self,index,x,y):

        if index!=0:
            row0=self.selectedRow
            time,temp=self.buildCoordinates(row0)

            """
            moving time of point 1,3,5 etc means changing rate and adding diff to dwell time
            moving time of point 2,4,6 etc means changing dwell time, and changing rate of the one after
           
            """

            step=(index-1)//2
            stepi=1+step*3
            rgi0=stepi+0
            lgi0=stepi+1
            tgi0=stepi+2
            if (index%2)==0:
                # 0,2,4 etc

                # change rate of next to compensate for time change of this
                # should cap but that time is constructed from rate and delta so max x would then be
                # time[index]+(temp[index+1]-temp[index])/rate

                # cap to minx and mintemp
                rate0=float(self.table.item(row0,rgi0).text())
                dy0=(y-temp[index-2])
                minx=time[index-2]
                if rate0: minx+=abs(dy0)/rate0
                x=max(minx,x)
                y=max(self.mintemp,y)

                if index+1<len(temp):
                    rgi1=rgi0+3
                    rg=float(self.table.item(row0,rgi1).text())

                    # cap to maxx and maxy
                    dx0=0
                    oldrate=rg
                    if oldrate: dx0=abs((temp[index+1]-temp[index]))/oldrate
                    maxx=time[index]+dx0
                    x=min(x,maxx)
                    # dwell time must not be negative so y might not change more than rate can fit until next level?
                    #maxy=temp[index-2]+(temp[index
                    if rate0:
                        maxy=temp[index-2]+(maxx-time[index-2])*rate0
                        y=min(maxy,y)

                    # change rate
                    dy1=temp[index+1]-y
                    dx1=time[index+1]-x
                    newrate=0.0
                    if dx1: newrate=abs(dy1/dx1)
                    else: newrate=9999.0

                    rg=newrate
                    newitem = QTableWidgetItem(str(rg))
                    self.table.setItem(row0,rgi1,newitem)
                    
                # compute dtime and dtemp with capped x and y
                dtime=x-time[index]
                dtemp=y-temp[index]
                
                # moving time means change dwell time
                dy0=y-temp[index-1]
                dt0=dy0/rate0
                tg=float(self.table.item(row0,tgi0).text())
                tg+=dtime-dt0
                newitem = QTableWidgetItem(str(tg))
                self.table.setItem(row0,tgi0,newitem)

                # change level
                lg=float(self.table.item(row0,lgi0).text())
                lg+=dtemp
                newitem = QTableWidgetItem(str(lg))
                self.table.setItem(row0,lgi0,newitem)
            else:                
                # 1,3,5 etc

                # change level of edited point and change rate of rise to compensate
                # cap x and y
                x=max(x,time[index-1])
                x=min(x,time[index+1])
                y=max(self.mintemp,y)
                dtime=x-time[index]
                dtemp=y-temp[index]
                    
                dy1=y-temp[index-1]
                dx1=x-time[index-1]
                newrate=0.0
                if dx1: newrate=abs(dy1/dx1)
                #newrate=max(0.01,newrate)

                # set new rate
                rg=float(self.table.item(row0,rgi0).text())                    
                rg=newrate                    
                newitem = QTableWidgetItem(str(rg))
                self.table.setItem(row0,rgi0,newitem)

                # set new level
                lg=float(self.table.item(row0,lgi0).text())
                lg+=dtemp
                newitem = QTableWidgetItem(str(lg))
                self.table.setItem(row0,lgi0,newitem)
                    
                # change dwell time to compensate for move of editied point and also change next rate to compensate
                tg=float(self.table.item(row0,tgi0).text())
                tg-=dtime
                newitem = QTableWidgetItem(str(tg))
                self.table.setItem(row0,tgi0,newitem)
                
                if index+2<len(temp):
                    dy1=temp[index+2]-y
                    dx1=time[index+2]-time[index+1]
                    newrate=0.0
                    if dx1: newrate=abs(dy1/dx1)
                    else: newrate=9999.0

                    # set new next rate?
                    rgi1=rgi0+3
                    rg=float(self.table.item(row0,rgi1).text())                    
                    rg=newrate                    
                    newitem = QTableWidgetItem(str(rg))
                    self.table.setItem(row0,rgi1,newitem)


                    
            time,temp=self.buildCoordinates(row0)
            newdata=np.array([time,temp])
            self.lineplot.plot_item_control.setData(time, temp)
            self.updateLinePlotTexts(time,temp)
                

    def setProgramParameters(self,name,program,row0):        
        for g in range(1,9):
            rn="r%d"%g
            ln="l%d"%g
            tn="t%d"%g
            rgi=1+(g-1)*3+0
            lgi=1+(g-1)*3+1
            tgi=1+(g-1)*3+2
            
            rg,lg,tg=-0.01,0.0,0.0

            newitem = QTableWidgetItem(name)
            self.table.setItem(row0,0,newitem)
            
            if g-1<len(program):
                rg,lg,tg=program[g-1]
                newitem = QTableWidgetItem(str(rg))
                self.table.setItem(row0,rgi,newitem)
                newitem = QTableWidgetItem(str(lg))
                self.table.setItem(row0,lgi,newitem)
                newitem = QTableWidgetItem(str(tg))
                self.table.setItem(row0,tgi,newitem)
                
    def getProgramParameters(self,row0):
        # r1,L1,d1
        # r2,L2,d2
        # ...
        program=[]

        name="program"
        try:
            name=self.table.item(row0,0).text()
        except:
            raise
        
        for g in range(1,9):
            rn="r%d"%g
            ln="l%d"%g
            tn="t%d"%g
            rgi=1+(g-1)*3+0
            lgi=1+(g-1)*3+1
            tgi=1+(g-1)*3+2

            rg,lg,tg=0.0,0.0,0.0
            
            try:
                rg=float(self.table.item(row0,rgi).text())
            except: pass
            try:
                lg=float(self.table.item(row0,lgi).text())
            except: pass
            try:
                tg=float(self.table.item(row0,tgi).text())
            except: pass
                        
            program+=[(rg,lg,tg)]
            
            # -0.01 as end of program
            # 0.0 is step ie skip slope and go straight to dwell
            if rg<0: break
        return name,program

        
    def buildCoordinates(self,row0):
        time=[]
        temp=[]

        t0=0.0
        templ=self.mintemp
        temp0=self.mintemp
        time+=[t0]
        temp+=[temp0]
        for g in range(1,9):
            rn="r%d"%g
            ln="l%d"%g
            tn="t%d"%g
            rgi=1+(g-1)*3+0
            lgi=1+(g-1)*3+1
            tgi=1+(g-1)*3+2

            rg,lg,tg=0.0,0.0,0.0
            
            try:
                rg=float(self.table.item(row0,rgi).text())
            except: pass
            try:
                lg=float(self.table.item(row0,lgi).text())
            except: pass
            try:
                tg=float(self.table.item(row0,tgi).text())
            except: pass

            if rg<0: break

            
            dtemp=abs(lg-templ)
            if rg==0: dttemp=0
            else:     dttemp=abs(dtemp/rg)

            #print(repr((rgi,lgi,tgi,rg,lg,tg,t0,dtemp,dttemp)))
            
            t0+=dttemp
            time+=[t0]
            temp+=[lg]
            
            t0+=tg
            time+=[t0]
            temp+=[lg]

            templ=lg
        return time,temp

    def updateLinePlotTexts(self,time,temp):
        if 1:
            for t in self.plottexts:
                self.lineplot.removeItem(t)
                del t
            self.plottexts=[]
        else:
            self.lineplot.clear()
        
            
        pen = pg.mkPen(color=(0, 0, 0))

        if 1:
            if self.lineplot.plot_item_control==None:
                self.lineplot.plot_item_control = self.lineplot.plot(time,temp, symbolBrush=(255,0,0), symbolPen='w')
            else:
                self.lineplot.plot_item_control.setData(time, temp)

        for i in range(1,len(time)):
            t0,t1=time[i-1],time[i]
            k0,k1=temp[i-1],temp[i]
            s=""
            rotateAxis=None
            if k0==k1:
                # dwell time at tempature
                s="%4.1fs at %4.1fC"%(t1-t0,k0)
                pos=(t0+t1)/2,k0
                angle=0
            elif t0!=t1:
                s="%.1fC/s"%((k1-k0)/(t1-t0))
                pos=(t0+t1)/2,(k0+k1)/2
                # angle
                # won't work since graph is not scale same in x and y
                angle=0 # math.atan2(pos[1],pos[0])*180/math.pi
                rotateAxis=(t1-t0,k1-k0)                
            else:
                # same time so just skip
                pass
            if s:
                text = pg.TextItem(s,color=(0,0,0),anchor=(0.5,1),angle=angle,rotateAxis=rotateAxis)
                text.setFont(self.tfont)
                self.lineplot.addItem(text)
                text.setPos(*pos)
                self.plottexts+=[text]

    
    @pyqtSlot(int,int)
    def tableSelectionChanged(self,row0,row1,text=None):
        #print("selection changed",row0,row1,self.cursorMoved)
        if self.cursorMoved: return

        # time = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        # temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]

        self.selectedRow=row0
        time,temp=self.buildCoordinates(row0)

        self.updateLinePlotTexts(time,temp)

            
            


        
    @pyqtSlot(int,int)
    def tableCellChanged(self,row,col,text):
        return self.realTableCellChanged(row,col,text)
    def realTableCellChanged(self,row,col,text):
        pass


    @pyqtSlot(str,int,int)
    def updateProgress(self,s,cmdp,p):
        # update action etc
        
        pass

    @pyqtSlot(dict)
    def updateState(self,state):
        # update enablement and such
        self.state=state
        for k,v in state.items():
            if k in self.infoslots:
                name,l,e=self.infoslots[k]
                # v needs to be processed and appenededlike C/s etc?
                e.setText(str(v))

        _os=state.get("OS","")
        if self.running and "program stopped" in _os:
            self.runningToStopped()
        if not self.running and "program running" in _os:
            self.stoppedToRunning()
        

    @pyqtSlot(dict)
    def updateRecord(self,state):
        # update enablement and such
        dt=state["dt"]
        value=state["value"]
        # feed graph and update it!!!

        tlist,vlist=self.runrecord
        tlist+=[dt]
        vlist+=[value]
        self.runrecord=(tlist,vlist)
        #print(self.runrecord)
        
        points=np.array([tlist,vlist])
        #print(points)
        self.scatter.setData(*points)
        self.scatter.updateSpots()
        self.scatter.invalidate()
        
                

    @pyqtSlot(str)
    def updateInfo(self,infopath):
        # update info about device
        pass
        
    def cmdlog(self,key,returncode,elements):
        print("key",repr(key),"returncode",returncode,"elem",elements)
    
    def statecallback(self,state):
        QMetaObject.invokeMethod(self,"updateState", Qt.QueuedConnection,Q_ARG(dict, state))

    def infocallback(self,infopath):
        QMetaObject.invokeMethod(self,"updateInfo", Qt.QueuedConnection,Q_ARG(str, infopath))

    def recordcallback(self,dt,value):
        QMetaObject.invokeMethod(self,"updateRecord", Qt.QueuedConnection,Q_ARG(dict, {"dt":dt,"value":value}))

        
if __name__ == '__main__':
    if 0:
        # run with fbs ie fbs run etc
        appctxt = ApplicationContext()       # 1. Instantiate ApplicationContext
        app=appctxt.app
    else:
        # run as plain python
        appctxt = QApplication(sys.argv)
        app=appctxt
        
    window = MainWindow(appctxt,sys.argv)
    window.resize(800, 600)
    window.show()
    exit_code = app.exec()      # 2. Invoke appctxt.app.exec()
    sys.exit(exit_code)
    
