 
/*  TwoFactorController
 *
 *  Copyright (C) 2023  Rene Pawlitzek, Robert Schoech
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import twofactor.Utils;

public class TwoFactorController {

    private Account account;
    
    private ScheduledExecutorService scheduledExecutorService;
    
    private int WINDOW_SIZE = 10;
    
    private String secretKey;
   
    @FXML
    private Button btCancelTan;
    
    @FXML
    private Button btLogin;

    @FXML
    private Button btSignUp;

    @FXML
    private ImageView ivQRCode;

    @FXML
    private Label lbLoginMessage;

    @FXML
    private Label lbSignUpMessage;
    
    @FXML
    private Label lbSecretKey;

    @FXML
    private Label lbTANMessage;

    @FXML
    private PasswordField pfLoginPassword;

    @FXML
    private PasswordField pfSignUpConfirmPassword;

    @FXML
    private PasswordField pfSignUpPassword;
     
    @FXML
    private TabPane tabPane;

    @FXML
    private TextField tfSignUpCompany;

    @FXML
    private TextField tfSignUpEmail;

    @FXML
    private TextField tfTAN;

    @FXML
    private TextField tfUsername;
    
    @FXML
    private LineChart<String, Number> lcTemperature;
  
    @FXML
    private void initialize() throws Exception {
        account = new Account();
        intializeTemperatureChart();
    }

    @FXML
    private void onSignUp(ActionEvent event) throws Exception  {
          if (("Sign Up").compareTo(btSignUp.getText()) == 0) {
              signUp();
         } else {
             confirmAuthentication();
         }
    }
   
    @FXML
    private void onLogin(ActionEvent event) {
        if (("Log In").compareTo(btLogin.getText()) == 0) {
             login();
         } else {
             tabPane.getTabs().get(0).setDisable(false);
             tabPane.getTabs().get(3).setDisable(true);
             resetLogin();
         }
    }
    
    @FXML
    private void onResetSignUp(ActionEvent event) {
        resetSignUp();
    }

    @FXML
    private void onTAN(ActionEvent event) {
        String code = tfTAN.getText();
        String secretKey = account.getSecretKey(tfUsername.getText());
        if (code.equals(Utils.getTOTPCode(secretKey))) {
            lbTANMessage.setText("2FA Log In successfully.");
            tabPane.getSelectionModel().getSelectedItem().setDisable(true);
            tabPane.getTabs().get(1).setDisable(false);
            tabPane.getTabs().get(2).setDisable(true);
            tabPane.getTabs().get(3).setDisable(false);
            
            // select Tab 'Temperature'
            tabPane.getSelectionModel().select(3);
        } else {
            lbTANMessage.setText("Invalid 2FA Code. Try again");
        }
    }
    
    @FXML
    private void onCancelTan(ActionEvent event) {
        resetLogin();
        resetCode();
        
        tabPane.getTabs().get(0).setDisable(false);
        tabPane.getTabs().get(1).setDisable(false);
        tabPane.getTabs().get(2).setDisable(true);
        
        // Select Tab 'Log In'
        tabPane.getSelectionModel().select(1);
    }
    
    private void login() {
        String name = tfUsername.getText();
        String pw = pfLoginPassword.getText();
                        
        if (account.verifyPassword(name, pw)) {
            tabPane.getTabs().get(0).setDisable(true);
            tabPane.getTabs().get(1).setDisable(true);
            tabPane.getTabs().get(2).setDisable(false);
            
            btLogin.setText("Log Out");
            lbLoginMessage.setText("You are logged in.");

            resetCode();
            // Select Tab '2FA Code'
            tabPane.getSelectionModel().select(2);
         } else {
            lbLoginMessage.setText("'Email' or 'Password' is wrong.");
         }
    }
    
    
    private void signUp() throws Exception  {
        var name = tfSignUpEmail.getText();
        if (name.isEmpty()) {
            lbSignUpMessage.setText("Type in email.");
            return;
        }
        
        if (account.verifyAccount(name)) {
            lbSignUpMessage.setText("Email " + name + " has already an account.");
            return;
        }
        
        var pw = pfSignUpPassword.getText().trim();
        if (pw.equals("")) {
            lbSignUpMessage.setText("Enter a plausible password.");
            return;
         }
        
        if (!pw.equals(pfSignUpConfirmPassword.getText())) {
            lbSignUpMessage.setText("Password and confirmed password are not identical");
            return;
        }
        
        var company = tfSignUpCompany.getText();
        secretKey = account.createSecretKey();
        var qr = account.createQRCode(secretKey, name, company);
        if (qr == null) {
            lbSignUpMessage.setText("New account is not possible.");
            return;
        } 
        var image = new Image(qr);
        ivQRCode.setImage(image);
        btSignUp.setText("Confirm");
        tfSignUpEmail.setDisable(true);
        tfSignUpCompany.setDisable(true);
        pfSignUpPassword.setDisable(true);
        pfSignUpConfirmPassword.setDisable(true);
        lbSecretKey.setText("Your setup key: " + secretKey);
        lbSignUpMessage.setText("Scan the QR code or add the setup key in Google Authenticator and click 'Confirm' after adding it.");
    }
    
    private void confirmAuthentication() throws Exception  {
        // add new account
        account.addAccount(tfSignUpEmail.getText(), tfSignUpCompany.getText(), pfSignUpPassword.getText(), secretKey);
        // select 'Log In'
        tabPane.getSelectionModel().select(1);
        tabPane.getTabs().get(0).setDisable(true);
        // reset tab 'SignUp'
        resetSignUp();
    }
    
    private void resetCode() {
        tfTAN.setText("");
        lbTANMessage.setText("Enter 2FA Code");
    }
    
    private void resetLogin() {
        tfUsername.setText("");
        pfLoginPassword.setText("");
        btLogin.setText("Log In");
        lbLoginMessage.setText("Login with your account.");
    }
    
    private void resetSignUp() {
        tfSignUpEmail.setText("");
        tfSignUpEmail.setDisable(false);
        tfSignUpEmail.setPromptText("test@gmail.com");
        
        tfSignUpCompany.setText("");
        tfSignUpCompany.setDisable(false);
        tfSignUpCompany.setPromptText("Awesome Company");
        
        pfSignUpPassword.setText("");
        pfSignUpPassword.setDisable(false);
        
        pfSignUpConfirmPassword.setText("");
        pfSignUpConfirmPassword.setDisable(false);
        
        btSignUp.setText("Sign Up");
        
        ivQRCode.setImage(null);
           lbSecretKey.setText("");

        lbSignUpMessage.setText("Create Account");
        
        account.deleteQRCode();
        
    }
    
    private void intializeTemperatureChart() {
        //defining the axes
        final CategoryAxis xAxis = new CategoryAxis(); // we are gonna plot against time
        final NumberAxis yAxis = new NumberAxis();
        
        xAxis.setLabel("Time/s");
        xAxis.setAnimated(false); // axis animations are removed
        yAxis.setLabel("Value");
        yAxis.setAnimated(false); // axis animations are removed
        
        lcTemperature.setTitle("Temperature Chart");
        lcTemperature.setAnimated(false);
        
        //defining a series to display data
        XYChart.Series<String, Number> series = new XYChart.Series<>();
        series.setName("Temperature Series");
        
        // add series to chart
        lcTemperature.getData().add(series);
        
        // this is used to display time in HH:mm:ss format
        final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");

        // setup a scheduled executor to periodically put data into the chart
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

        // put dummy data onto graph per second
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            // get a random integer between 0-10
            Integer random = ThreadLocalRandom.current().nextInt(25,30);

            // Update the chart
            Platform.runLater(() -> {
                // get current time
                Date now = new Date();
                // put random number with current time
                series.getData().add(new XYChart.Data<>(simpleDateFormat.format(now), random));

                if (series.getData().size() > WINDOW_SIZE)
                    series.getData().remove(0);
            });
        }, 0, 5, TimeUnit.SECONDS);

    }
    
}