package ch.hevs.isi.field;

import ch.hevs.isi.core.DataPoint;
import ch.hevs.isi.core.DataPointListener;
import ch.hevs.isi.core.FloatDataPoint;
import ch.hevs.isi.utils.Utility;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;

public class FieldConnector implements DataPointListener {
    private static FieldConnector mySelf = null;

    /**
     * private constructor
     * initialize the FieldConnector
     * it connects the ModbusAccessor, reads the csv-file of the ModbusMap
     * and starts the periodical polling of the modbus registers
     */
    private FieldConnector(){
        initialize("LocalHost",1502, "src/main/resources/ModbusMap.csv");


        // Subscribe to the update of DataPoints
        DataPointListener.subscribeUpdate(this);
    }

    /**
     * static method to create a singleton pattern of the class
     * checks if an instance of the class is already made
     * if not, it creates an instance of the class FieldConnector
     * @return instance of FieldConnector
     */
    public static FieldConnector getMySelf(){
        if (mySelf == null){
            mySelf = new FieldConnector();
        }
        return mySelf;
    }

    /**
     * read the csv-file of the ModbusMap
     * @param pathToFile path to the file of all modbus registers (C:/.../ModbusMap.csv)
     */
    public static void createRegister(String pathToFile){
        try{
            //BufferedReader csvFile = new BufferedReader(new FileReader(pathToFile));
            BufferedReader csvFile = Utility.fileParser(null, "ModbusMap.csv");
            csvFile.readLine();

            while(csvFile.ready()){
                String line = csvFile.readLine();                   // read one line
                String[] splitLine = line.split(";");         // split line between ";"

                String label = splitLine[0];                        // first split is the label of the register
                boolean isOutput = splitLine[3].equals("Y");        // third split declares if it is an output
                int address = new Integer((splitLine[4]));          // fourth split is the address of the register
                int range = new Integer(splitLine[5]);              // if it is a floatDatapoint, the fifth split is the range of the data
                int offset = new Integer(splitLine[6]);             // if it is a floatDatapoint, the sixth split is the offset of the data


                // create a float or a boolean register and put it in the map of registers
                if (splitLine[1].equals("B")){
                    BooleanRegister b = new BooleanRegister(label, isOutput, address);
                    b.updateMapOfRegisters(label, address);             //save the register in map, the key is the label of the datapoint
                } else if(splitLine[1].equals("F")){
                    FloatRegister f = new FloatRegister(label, isOutput, address, range, offset);
                    f.updateMapOfRegisters(label, address);             //save the register in map, the key is the label of the datapoint
                }
            }
        }
        catch(FileNotFoundException e){
            e.printStackTrace();
        }
        catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * this method initialize the fieldConnector
     * it connects the ModbusAccessor, reads the csv-file of the ModbusMap
     * and starts the periodical polling of the modbus registers
     * @param host  ip address of the server
     * @param port  port of the server
     * @param pathToFile path to the file of all modbus registers (C:/.../ModbusMap.csv)
     */
    public void initialize(String host, int port, String pathToFile){

        ModbusAccessor.getMySelf().connect(host,port);          //connect to the modbus server
        createRegister(pathToFile);                             //create the all needed modbus registers and save them in a map
        startPeriodicalPolling();                               //start periodical reading of the values

    }

    /**
     * this function is only to show the datapoint, which is pushed to filed in the onNewValue function
     * @param label label of the datapoint
     * @param value value of the datapoint
     */
    private void pushToField(String label, String value){
        Utility.pDebug("Field: " + label + " " + value);
    }
    @Override
    /**
     * everytime a new value should be writen by any connector, this function is called
     * updates the value of the datapoint on the field
     */
    public void onNewValue(DataPoint dp) {
        ModbusRegister mR = ModbusRegister.getRegisterFromDatapoint(dp);    //search the corresponding register to the datapoint
        if(mR == null) return; //if the register is not found, return

        if(dp.isOutput()){  //write only on the datapoints, which are outputs
            if(dp.getLabel().equals("REMOTE_SOLAR_SW")      //write only boolean outputs
                ||dp.getLabel().equals("REMOTE_WIND_SW")
                ||dp.getLabel().equals("SOLAR_CONNECT_ST")
                ||dp.getLabel().equals("WIND_CONNECT_ST")){
                BooleanRegister br = (BooleanRegister) ModbusRegister.getRegisterFromDatapoint(dp);     //create a booleanregister, because they are only boolean datapoints
                br.write(dp);   //write with the correct dp value
            }else{      //write only float outputs
                FloatRegister fr = (FloatRegister) ModbusRegister.getRegisterFromDatapoint(dp);         //create a floatregister, because they are only float datapoints
                fr.write(dp);   //write with the correct dp value
            }
            pushToField(dp.getLabel(),dp.toString());
        }
    }

    /**
     * method to start a periodical task
     * in our case it is the reading of the modbus registers
     */
    public void startPeriodicalPolling(){
        Timer pollTimer = new Timer();      //create a new timer,
        PollTask pollTask = new PollTask();     //create a new task to do every period.
        pollTimer.scheduleAtFixedRate(pollTask,0,100); //do the run-function of the polltask every period

    }
}
