//
//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
//
//  This 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 2 of the License, or
//  (at your option) any later version.
//
//  This software 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 software; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
//  USA.
//
package nxm.vnc.libvnc.tightvnc;

import java.awt.Button;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.lang.reflect.Method;

import nxm.sys.inc.LimitedFutureSupport;
import nxm.sys.lib.ReflectUtil;

/**
   Recording frame. It allows to control recording RFB sessions into
   FBS (FrameBuffer Stream) files.

   2006-07-08 whk Only mod to file is addition of package label
*/
class RecordingFrame extends Frame
  implements WindowListener, ActionListener {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2013030808332L; // for Serializable interface

  // SecurityManager and methods are accedd via reflection, so there are no failures when removed
  private static Method GETSECURITYMANAGER_METHOD = ReflectUtil.getMethodOfClass("java.lang.System", "getSecurityManager");
  private static Method CHECKPROPERTYACCESS_METHOD = ReflectUtil.getMethodOfClass("java.lang.SecurityManager", "checkPropertyAccess",String.class);

  boolean recording;

  TextField fnameField;
  Button browseButton;

  Label statusLabel;

  Button recordButton, nextButton, closeButton;
  VncViewer viewer;

  //
  // Check if current security manager allows to create a
  // RecordingFrame object.
  //

  @LimitedFutureSupport("JDK17+ has SecurityManager deprecated for removal")
  public static boolean checkSecurity() {
    if (GETSECURITYMANAGER_METHOD==null) return true; // Deprecated API has been removed
    try {
      Object security = GETSECURITYMANAGER_METHOD.invoke(null);
      if (security != null) {
        try {
          CHECKPROPERTYACCESS_METHOD.invoke(security, "user.dir");
          CHECKPROPERTYACCESS_METHOD.invoke(security, "file.separator");
          // Work around (rare) checkPropertyAccess bug
          System.getProperty("user.dir");
        } catch (SecurityException e) {
          System.out.println("SecurityManager restricts session recording.");
          return false;
        }
      }
      return true;
    }
    catch (Exception e) {
      System.out.println("Exception using reflection for SystemManager or methods:"+e);
      return true;
    }
  }

  //
  // Constructor.
  //

  RecordingFrame(VncViewer v) {
    super("TightVNC Session Recording");

    viewer = v;

    // Determine initial filename for next saved session.
    // FIXME: Check SecurityManager.

    String fname = nextNewFilename(System.getProperty("user.dir") +
                                   System.getProperty("file.separator") +
                                   "vncsession.fbs");

    // Construct new panel with file name field and "Browse" button.

    Panel fnamePanel = new Panel();
    GridBagLayout fnameGridbag = new GridBagLayout();
    fnamePanel.setLayout(fnameGridbag);

    GridBagConstraints fnameConstraints = new GridBagConstraints();
    fnameConstraints.gridwidth = GridBagConstraints.RELATIVE;
    fnameConstraints.fill = GridBagConstraints.BOTH;
    fnameConstraints.weightx = 4.0;

    fnameField = new TextField(fname, 64);
    fnameGridbag.setConstraints(fnameField, fnameConstraints);
    fnamePanel.add(fnameField);
    fnameField.addActionListener(this);

    fnameConstraints.gridwidth = GridBagConstraints.REMAINDER;
    fnameConstraints.weightx = 1.0;

    browseButton = new Button("Browse");
    fnameGridbag.setConstraints(browseButton, fnameConstraints);
    fnamePanel.add(browseButton);
    browseButton.addActionListener(this);

    // Construct the frame.

    GridBagLayout gridbag = new GridBagLayout();
    setLayout(gridbag);

    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridwidth = GridBagConstraints.REMAINDER;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.weighty = 1.0;
    gbc.insets = new Insets(10, 0, 0, 0);

    Label helpLabel =
      new Label("File name to save next recorded session in:", Label.CENTER);
    gridbag.setConstraints(helpLabel, gbc);
    add(helpLabel);

    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weighty = 0.0;
    gbc.insets = new Insets(0, 0, 0, 0);

    gridbag.setConstraints(fnamePanel, gbc);
    add(fnamePanel);

    gbc.fill = GridBagConstraints.BOTH;
    gbc.weighty = 1.0;
    gbc.insets = new Insets(10, 0, 10, 0);

    statusLabel = new Label("", Label.CENTER);
    gridbag.setConstraints(statusLabel, gbc);
    add(statusLabel);

    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weightx = 1.0;
    gbc.weighty = 0.0;
    gbc.gridwidth = 1;
    gbc.insets = new Insets(0, 0, 0, 0);

    recordButton = new Button("Record");
    gridbag.setConstraints(recordButton, gbc);
    add(recordButton);
    recordButton.addActionListener(this);

    nextButton = new Button("Next file");
    gridbag.setConstraints(nextButton, gbc);
    add(nextButton);
    nextButton.addActionListener(this);

    closeButton = new Button("Close");
    gridbag.setConstraints(closeButton, gbc);
    add(closeButton);
    closeButton.addActionListener(this);

    // Set correct text, font and color for the statusLabel.
    stopRecording();

    pack();

    addWindowListener(this);
  }

  //
  // If the given string ends with ".NNN" where NNN is a decimal
  // number, increase this number by one. Otherwise, append ".001"
  // to the given string.
  //

  protected String nextFilename(String fname) {
    int len = fname.length();
    int suffixPos = len;
    int suffixNum = 1;

    if (len > 4 && fname.charAt(len - 4) == '.') {
      try {
	suffixNum = Integer.parseInt(fname.substring(len - 3, len)) + 1;
	suffixPos = len - 4;
      } catch (NumberFormatException e) { }
    }

    char[] zeroes = {'0', '0', '0'};
    String suffix = String.valueOf(suffixNum);
    if (suffix.length() < 3) {
      suffix = new String(zeroes, 0, 3 - suffix.length()) + suffix;
    }

    return fname.substring(0, suffixPos) + '.' + suffix;
  }

  //
  // Find next name of a file which does not exist yet.
  //

  protected String nextNewFilename(String fname) {
    String newName = fname;
    File f;
    try {
      do {
	newName = nextFilename(newName);
	f = new File(newName);
      } while (f.exists());
    } catch (SecurityException e) { }

    return newName;
  }

  //
  // Let the user choose a file name showing a FileDialog.
  //

  protected boolean browseFile() {
    File currentFile = new File(fnameField.getText());

    FileDialog fd =
      new FileDialog(this, "Save next session as...", FileDialog.SAVE);
    fd.setDirectory(currentFile.getParent());
    fd.setVisible(true);
    if (fd.getFile() != null) {
      String newDir = fd.getDirectory();
      String sep = System.getProperty("file.separator");
      if (newDir.length() > 0) {
	if (!sep.equals(newDir.substring(newDir.length() - sep.length())))
	  newDir += sep;
      }
      String newFname = newDir + fd.getFile();
      if (newFname.equals(fnameField.getText())) {
	fnameField.setText(newFname);
	return true;
      }
    }
    return false;
  }

  //
  // Start recording.
  //

  public void startRecording() {
    statusLabel.setText("Status: Recording...");
    statusLabel.setFont(new Font("Helvetica", Font.BOLD, 12));
    statusLabel.setForeground(Color.red);
    recordButton.setLabel("Stop recording");

    recording = true;

    viewer.setRecordingStatus(fnameField.getText());
  }

  //
  // Stop recording.
  //

  public void stopRecording() {
    statusLabel.setText("Status: Not recording.");
    statusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
    statusLabel.setForeground(Color.black);
    recordButton.setLabel("Record");

    recording = false;

    viewer.setRecordingStatus(null);
  }

  //
  // Close our window properly.
  //

  public void windowClosing(WindowEvent evt) {
    setVisible(false);
  }

  //
  // Ignore window events we're not interested in.
  //

  public void windowActivated(WindowEvent evt) {}
  public void windowDeactivated (WindowEvent evt) {}
  public void windowOpened(WindowEvent evt) {}
  public void windowClosed(WindowEvent evt) {}
  public void windowIconified(WindowEvent evt) {}
  public void windowDeiconified(WindowEvent evt) {}


  //
  // Respond to button presses
  //

  public void actionPerformed(ActionEvent evt) {
    if (evt.getSource() == browseButton) {
      if (browseFile() && recording)
	startRecording();

    } else if (evt.getSource() == recordButton) {
      if (!recording) {
	startRecording();
      } else {
	stopRecording();
        fnameField.setText(nextNewFilename(fnameField.getText()));
      }

    } else if (evt.getSource() == nextButton) {
      fnameField.setText(nextNewFilename(fnameField.getText()));
      if (recording)
	startRecording();

    } else if (evt.getSource() == closeButton) {
      setVisible(false);

    }
  }
}
