package org.free.j64.gui;

import static org.free.j64.app.Desktop.debugFile;
import static org.free.j64.app.Desktop.saveDump;
import static org.free.j64.app.Utils.a65;
import static org.free.j64.app.Utils.bin;
import static org.free.j64.app.Utils.dumpMsg;
import static org.free.j64.app.Utils.getExtension;
import static org.free.j64.app.Utils.getText;
import static org.free.j64.app.Utils.initWindow;
import static org.free.j64.app.Utils.isAcceptAssembly;
import static org.free.j64.app.Utils.isAcceptBinary;
import static org.free.j64.app.Utils.localDate;
import static org.free.j64.app.Utils.setText;
import static org.free.j64.app.Utils.showChooser;
import static org.free.j64.app.Utils.txt;
import static org.free.j64.app.Utils.validateInt;

import java.awt.Frame;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import javax.swing.JFileChooser;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileFilter;

import org.free.j64.cpu.Assembler;
import org.free.j64.cpu.CPU;
import org.free.j64.graphics.Screen;

/**
 *
 * @author osman
 */
public final class Patch extends javax.swing.JDialog {

	private static final String scriptFile = String.format("c64-script-%s.txt",
		localDate);
	private static final FileFilter openAssembly = new FileFilter() {
		@Override
		public boolean accept(File f) {
			return isAcceptAssembly(f);
		}

		@Override
		public String getDescription() {
			return "Assembly files (a65|bin|txt)";
		}
	};
	private static final FileFilter saveBinary = new FileFilter() {
		@Override
		public boolean accept(File f) {
			return isAcceptBinary(f);
		}

		@Override
		public String getDescription() {
			return "Binary files (bin)";
		}
	};
	private final Frame parent;
	private final Screen screen;
	private final CPU cpu;

	public Patch(Screen screen, CPU cpu, java.awt.Frame parent, boolean modal) {
		super(parent, modal);
		this.parent = parent;
		this.screen = screen;
		this.cpu = cpu;
		initComponents();
		initWindow(this, getRootPane(), jButton1);
		jButton1.requestFocusInWindow();
		setVisible(true);
	}

	private void dumpPatch(int from, int to, boolean isHex) {
		String strPatch = (isHex) ? "PATCH HEX %04x%n%s%n/PATCH%n"
			: "PATCH DEC %d%n%s%n/PATCH%n";

		dumpPatch(String.format(strPatch, from,
			screen.dumpRawMemory(from, to - from, isHex)), from, to);
	}

	private void dumpPatch(String msg, int from, int to) {
		dumpMemory(from, to, saveDump(), debugFile);
		dumpMsg(msg, saveDump(), scriptFile);
	}

	private void dumpMemory(int from, int to, boolean save, String file) {
		String localTime = new SimpleDateFormat("yy-MM-dd hh:mm:ss", Locale.ENGLISH)
			.format(new Date());
		String header = String.format("%s%nMEMORY DUMP %s%n%s%n",
			org.free.j64.app.Desktop.DEBUG_BORDER, localTime,
			org.free.j64.app.Desktop.DEBUG_BORDER);
		String msg = String.format("%s%s", header,
			screen.dumpMemory(from, to - from));

		dumpMsg(msg, save, file);
	}

	private void fillMemory() {
		int from = validateInt(jTextField1.getText(), 10, 0, 65536), to = validateInt(
			jTextField2.getText(), 10, 0, 65536), value = validateInt(
				jTextField3.getText(), 10, 0, 256);

		if (!(from == -1 || to == -1 || from > to || value == -1)) {
			new Assembler(cpu, screen).fillMemory(from, to, value);

			String strFill;

			if (jCheckBox1.isSelected()) {
				strFill = "FILL HEX %04x %02x %02x%n";
			} else {
				strFill = "FILL DEC %d %d %d%n";
			}

			dumpPatch(String.format(strFill, from, to, value), from, to);

		} else {
			System.err
				.format("Fill memory locations: 0 <= from < to < 65536 and 0 <= value < 256%n");
		}
	}

	private void openAssemblyDialog() {
		jFileChooser1.setDialogTitle("Open C64 Assembly File");
		jFileChooser1.setAcceptAllFileFilterUsed(false);
		jFileChooser1.setFileFilter(openAssembly);
		jFileChooser1.removeChoosableFileFilter(saveBinary);
		showChooser(jFileChooser1);
	}

	private void openAssembly() {
		final int from = validateInt(jTextField4.getText(), 10, 0, 65536);

		if (from != -1) {
			openAssemblyDialog();

			if (jFileChooser1.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
				final String path = jFileChooser1.getSelectedFile().toString();
				String name = jFileChooser1.getSelectedFile().getName();
				final String ext = getExtension(name);

				jButton6.setEnabled(false);
				final boolean isHex = jCheckBox1.isSelected();

				new SwingWorker<Void, Void>() {
					@Override
					protected Void doInBackground() {
						if (ext.equalsIgnoreCase(a65)) {
							dumpPatch(from, new Assembler(cpu, screen).assemble(getText(path), from),
								isHex);

						} else if (ext.equalsIgnoreCase(bin)) {
							dumpPatch(from, new Assembler(cpu, screen).loadBinary(path, from), isHex);

						} else if (ext.equalsIgnoreCase(txt)) {
							new Assembler(cpu, screen).loadScript(path); // dump per command in script
						}

						return null;
					}

					@Override
					protected void done() {
						jButton6.setEnabled(true);
					}
				}.execute();
			}

			jFileChooser1.setVisible(false);

		} else {
			System.err.format("Patch memory location: 0 <= from < 65536%n");
		}
	}

	private void saveBinaryDialog() {
		jFileChooser1.setDialogTitle("Save C64 Binary File");
		jFileChooser1.setAcceptAllFileFilterUsed(false);
		jFileChooser1.setFileFilter(saveBinary);
		jFileChooser1.removeChoosableFileFilter(openAssembly);
		showChooser(jFileChooser1);
	}

	private void saveBinary() {
		final int from = validateInt(jTextField5.getText(), 10, 0, 65536), to = validateInt(
			jTextField6.getText(), 10, 0, 65536);

		if (!(from == -1 || to == -1 || from > to)) {
			saveBinaryDialog();

			if (jFileChooser1.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
				String path = jFileChooser1.getSelectedFile().toString();
				String name = jFileChooser1.getSelectedFile().getName();

				if (!getExtension(name).equalsIgnoreCase(bin)) {
					path = String.format("%s.%s", path, bin);
				}

				final String p = path;
				jButton5.setEnabled(false);

				new SwingWorker<Void, Void>() {
					@Override
					protected Void doInBackground() {
						new Assembler(cpu, screen).saveBinary(p, from, to);
						return null;
					}

					@Override
					protected void done() {
						jButton5.setEnabled(true);
					}
				}.execute();
			}

			jFileChooser1.setVisible(false);

		} else {
			System.err.format("Save memory locations: 0 <= from < to < 65536%n");
		}
	}

	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.
	 */
	@SuppressWarnings("unchecked")
	// <editor-fold defaultstate="collapsed"
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    jFileChooser1 = new javax.swing.JFileChooser();
    jLabel2 = new javax.swing.JLabel();
    jLabel3 = new javax.swing.JLabel();
    jTextField2 = new javax.swing.JTextField();
    jTextField1 = new javax.swing.JTextField();
    jButton1 = new javax.swing.JButton();
    jButton2 = new javax.swing.JButton();
    jLabel5 = new javax.swing.JLabel();
    jTextField3 = new javax.swing.JTextField();
    jLabel7 = new javax.swing.JLabel();
    jTextField4 = new javax.swing.JTextField();
    jButton4 = new javax.swing.JButton();
    jButton5 = new javax.swing.JButton();
    jButton6 = new javax.swing.JButton();
    jLabel4 = new javax.swing.JLabel();
    jTextField5 = new javax.swing.JTextField();
    jTextField6 = new javax.swing.JTextField();
    jLabel6 = new javax.swing.JLabel();
    jCheckBox1 = new javax.swing.JCheckBox();

    setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
    setTitle("Patch Memory");
    setResizable(false);

    jLabel2.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel2.setText("From");

    jLabel3.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel3.setText("To");

    jTextField2.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField2.setText("65535");
    jTextField2.setToolTipText("Prefix Hex Numbers with $");
    jTextField2.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jTextField1.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField1.setText("0");
    jTextField1.setToolTipText("Prefix Hex Numbers with $");
    jTextField1.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jButton1.setMnemonic('C');
    jButton1.setText("Close");
    jButton1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        Patch.this.actionPerformed(evt);
      }
    });

    jButton2.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jButton2.setMnemonic('F');
    jButton2.setText("Fill");
    jButton2.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        Patch.this.actionPerformed(evt);
      }
    });

    jLabel5.setDisplayedMnemonic('V');
    jLabel5.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel5.setText("Value");

    jTextField3.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField3.setText("0");
    jTextField3.setToolTipText("Prefix Hex Numbers with $");
    jTextField3.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jLabel7.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel7.setText("From");

    jTextField4.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField4.setText("0");
    jTextField4.setToolTipText("Prefix Hex Numbers with $");
    jTextField4.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jButton4.setMnemonic('D');
    jButton4.setText("Dump");
    jButton4.setToolTipText("");
    jButton4.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        Patch.this.actionPerformed(evt);
      }
    });

    jButton5.setMnemonic('S');
    jButton5.setText("Save");
    jButton5.setToolTipText("");
    jButton5.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        Patch.this.actionPerformed(evt);
      }
    });

    jButton6.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jButton6.setMnemonic('P');
    jButton6.setText("Patch");
    jButton6.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        Patch.this.actionPerformed(evt);
      }
    });

    jLabel4.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel4.setText("From");

    jTextField5.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField5.setText("0");
    jTextField5.setToolTipText("Prefix Hex Numbers with $");
    jTextField5.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jTextField6.setFont(new java.awt.Font("Dialog", 1, 16)); // NOI18N
    jTextField6.setText("65535");
    jTextField6.setToolTipText("Prefix Hex Numbers with $");
    jTextField6.addFocusListener(new java.awt.event.FocusAdapter() {
      public void focusLost(java.awt.event.FocusEvent evt) {
        Patch.this.focusLost(evt);
      }
    });

    jLabel6.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
    jLabel6.setText("To");

    jCheckBox1.setFont(new java.awt.Font("Dialog", 1, 13)); // NOI18N
    jCheckBox1.setMnemonic('H');
    jCheckBox1.setSelected(true);
    jCheckBox1.setText("Hex Dump");

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(jLabel4)
                  .addComponent(jTextField5, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(jLabel6)
                  .addGroup(layout.createSequentialGroup()
                    .addComponent(jTextField6, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                    .addComponent(jButton5))))
              .addComponent(jLabel7)
              .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addGroup(layout.createSequentialGroup()
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                      .addComponent(jLabel2)
                      .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                      .addComponent(jLabel3)
                      .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE)))
                  .addGroup(layout.createSequentialGroup()
                    .addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(jButton6)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                  .addComponent(jCheckBox1)
                  .addComponent(jLabel5)
                  .addGroup(layout.createSequentialGroup()
                    .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                    .addComponent(jButton2))))))
          .addGroup(layout.createSequentialGroup()
            .addGap(103, 103, 103)
            .addComponent(jButton4)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addComponent(jButton1)))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jButton1, jButton2, jButton4, jButton5, jButton6});

    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
          .addGroup(layout.createSequentialGroup()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jLabel2)
              .addComponent(jLabel3))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
              .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
          .addGroup(layout.createSequentialGroup()
            .addComponent(jLabel5)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
          .addComponent(jButton2))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabel7)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jButton6)
          .addComponent(jCheckBox1))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jLabel4)
          .addComponent(jLabel6))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
            .addComponent(jTextField6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addComponent(jButton5))
          .addComponent(jTextField5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
          .addComponent(jButton4)
          .addComponent(jButton1))
        .addContainerGap())
    );

    layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {jButton1, jButton2, jButton4, jButton5, jButton6});

    pack();
  }// </editor-fold>//GEN-END:initComponents

	private void actionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_actionPerformed
		String cmd = evt.getActionCommand();

		if (cmd.startsWith("Close")) {
			setVisible(false);
		} else if (cmd.startsWith("Dump")) {
			new Dump(screen, parent, false);
		} else if (cmd.startsWith("Fill")) {
			fillMemory();
		} else if (cmd.startsWith("Patch")) {
			openAssembly();
		} else if (cmd.startsWith("Save")) {
			saveBinary();
		}

	}// GEN-LAST:event_actionPerformed

	private void focusLost(java.awt.event.FocusEvent evt) {// GEN-FIRST:event_focusLost
		if (evt.getSource() instanceof JTextField) {
			JTextField o = (JTextField) evt.getSource();
			o.setText(o.getText().toUpperCase());
		}

		if (evt.getSource().equals(jTextField1)) {
			setText(jTextField1, jTextField2);
		} else if (evt.getSource().equals(jTextField5)) {
			setText(jTextField5, jTextField6);
		}
	}// GEN-LAST:event_focusLost
  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton jButton1;
  private javax.swing.JButton jButton2;
  private javax.swing.JButton jButton4;
  private javax.swing.JButton jButton5;
  private javax.swing.JButton jButton6;
  private javax.swing.JCheckBox jCheckBox1;
  private javax.swing.JFileChooser jFileChooser1;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JLabel jLabel3;
  private javax.swing.JLabel jLabel4;
  private javax.swing.JLabel jLabel5;
  private javax.swing.JLabel jLabel6;
  private javax.swing.JLabel jLabel7;
  private javax.swing.JTextField jTextField1;
  private javax.swing.JTextField jTextField2;
  private javax.swing.JTextField jTextField3;
  private javax.swing.JTextField jTextField4;
  private javax.swing.JTextField jTextField5;
  private javax.swing.JTextField jTextField6;
  // End of variables declaration//GEN-END:variables
}
