
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;


import javax.swing.*;
import java.awt.*;


public class MainHexEditor extends JFrame {

  public MainHexEditor () {
    setSize(500, 300);
    setTitle("HexEditor");
    setGUI();
    setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  }

 private void setGUI() {
    HexEditor hex = new HexEditor();
    setLayout( new BorderLayout() );
    this.getContentPane().add(hex, BorderLayout.CENTER);
   try {
   hex.loadFromFile( new File("MainHexEditor.java") );
   }
  catch (IOException e) {
  }
 }


  public static void main(String[] args) {
    MainHexEditor frame = new MainHexEditor();
    frame.setVisible(true);
  }


}

class HexEditor extends JComponent
{
	private class KeyboardListener implements KeyListener
	{

		@Override
		public void keyPressed(KeyEvent e) {
			int code = e.getKeyCode();
			
			if(code == KeyEvent.VK_UP)
			{
				Key_Up();
			}
			else if(code == KeyEvent.VK_DOWN)
			{
				Key_Down();
			}
			else if(code == KeyEvent.VK_LEFT)
			{
				Key_Left();
			}
			else if(code == KeyEvent.VK_RIGHT)
			{
				Key_Right();
			}
			else if(KeyEvent.VK_0 <= code && code <= KeyEvent.VK_9)
			{
				Change_Value(code - 48);
				Key_Right();
			}
			else if(KeyEvent.VK_A <= code && code <= KeyEvent.VK_F)
			{
				Change_Value(code - 55);
				Key_Right();
			}
			else if(code == KeyEvent.VK_DELETE)
			{
				Delete_Value();
				Key_Left();
				Key_Left();
			}
			
			if(getParent().getClass() == JViewport.class)
			{
				Adjust_Viewport();
			}
			repaint();
		}

		@Override
		public void keyReleased(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void keyTyped(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}
		
	}
	private List<Byte> _data = new Vector<Byte>();
	
	private Point _selection = new Point();
	
	//Daten zum Zeichnen
	
	private int _charDescent;
	private int _charHeight;
	private int _charWidth;
	private Point _colOffset = new Point();
	private Point _rowOffset = new Point();
	
	public HexEditor()
	{
		super();
		this.setDoubleBuffered(true);
		this.setFocusable(true);
		this.setFont(new Font("Monospaced", Font.PLAIN, 13));
		this.addKeyListener(new KeyboardListener());
	}
	
	//Private Methoden
	
	private int getRowCount()
	{
		return _data.size() / 16 + 1;
	}
	
	private void Adjust_Viewport()
	{
		JViewport parent = (JViewport) getParent();
		Rectangle visibleRect = parent.getViewRect();
		Rectangle selectedRect = new Rectangle(_colOffset.x + (_selection.x / 2) * 3 * _charWidth + (_selection.x % 2) * _charWidth, _rowOffset.y + (_selection.y * _charHeight) - _charHeight + _charDescent, _charWidth, _charHeight);
		
		if(!visibleRect.contains(selectedRect))
		{
			parent.setViewPosition(new Point(selectedRect.x, selectedRect.y));
			
		}
	}
	
	private void Key_Down()
	{
		if(_selection.y == _data.size() / 16)
		{
			_selection.x = 2 * (_data.size() % 16);
		}
		else
		{
			_selection.y++;
			if(_selection.x >  2 * (_data.size() % 16))
			{
				_selection.x = 2 * (_data.size() % 16);
			}
		}
	}
	
	private void Key_Left()
	{
		if(_selection.x > 0)
		{
			_selection.x--;
		}
		else if(_selection.y > 0)
		{
			_selection.x = 31;
			_selection.y--;
		}
	}
	
	private void Key_Right()
	{	
		if(_selection.y == _data.size() / 16)
		{
			if((_selection.x / 2) < _data.size() % 16)
			{
				_selection.x++;
			}
		}
		else
		{
			if(_selection.x == 31)
			{
				_selection.x = 0;
				_selection.y++;
			}
			else
			{
				_selection.x++;
			}
		}
	}
	
	private void Key_Up()
	{
		if(_selection.y > 0)
		{
			_selection.y--;
		}
		else
		{
			_selection.x = 0;
		}
	}
	
	private void Change_Value(int newVal)
	{
		if((_selection.x / 2) == _data.size() % 16 && _selection.y == _data.size() / 16)
		{
			_data.add(new Integer(newVal << 4).byteValue());
			this.updateDimension();
		}
		else
		{
			int value = _data.get(_selection.x / 2 + _selection.y * 16);
			if(_selection.x % 2 > 0)
			{
				value &= 0xF0;
				value |= newVal;
			}
			else
			{
				value &= 0x0F;
				value |= newVal << 4;
			}
			_data.set(_selection.x / 2 + _selection.y * 16, new Integer(value).byteValue());
		}
	}
	
	private void Delete_Value()
	{
		if(!((_selection.x / 2) == _data.size() % 16 && _selection.y == _data.size() / 16))
		{
			_data.remove(_selection.x / 2 + _selection.y * 16);
			this.updateDimension();
		}
	}
	
	private void updateOffets()
	{
		FontMetrics fm = this.getFontMetrics(this.getFont());
		
		_charDescent = fm.getDescent();
		_charHeight = fm.getHeight();
		_charWidth = fm.stringWidth("D");
		
		_colOffset.x = 11 * _charWidth;
		_colOffset.y = _charHeight;
		
		_rowOffset.x = _charWidth;
		_rowOffset.y = 2 * _charHeight;
	}
	
	private void updateDimension()
	{
		this.setPreferredSize(new Dimension(76 * _charWidth, _charHeight + this.getRowCount() * _charHeight));
	}
	
	//Protected Methoden
	
	protected void paintColIndex(Graphics g)
	{
		g.setFont(this.getFont().deriveFont(Font.BOLD));
		
		int x = _colOffset.x;
		int y = _colOffset.y;
		
		for(int i = 0; i < 16; i++)
		{
			g.drawString(String.format("%02X", i), x, y);
			x += 3 * _charWidth;
		}
	}
	
	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		this.paintSelection(g);
		this.paintColIndex(g);
		this.paintData(g);
		this.paintString(g);
		this.paintRowIndex(g);
	}
	
	protected void paintData(Graphics g)
	{
		g.setFont(this.getFont());
		
		int x = _colOffset.x;
		int y = _rowOffset.y;
		int rows = this.getRowCount();
		
		for(int i = 0; i < rows; i++)
		{
			for(int j = 0; j < 16 && j + (i << 4) < _data.size(); j++)
			{
				g.drawString(String.format("%02X", _data.get(i * 16 + j)), x, y);
				x += 3 * _charWidth;
			}
			x = _colOffset.x;
			y += _charHeight;
		}
		
		g.drawString("??", _colOffset.x + _data.size() % 16 * 3 * _charWidth, _rowOffset.y + _data.size() / 16 * _charHeight);
	}
	
	protected void paintRowIndex(Graphics g)
	{
		g.setFont(this.getFont().deriveFont(Font.BOLD));
		
		int x = _rowOffset.x;
		int y = _rowOffset.y;
		int rows = this.getRowCount();
		
		for(int i = 0; i < rows; i++)
		{
			g.drawString(String.format("%08X", i), x, y);
			y += _charHeight;
		}
	}
	
	protected void paintSelection(Graphics g)
	{
		g.setColor(Color.RED);
		g.fillRect(_colOffset.x + (_selection.x / 2) * 3 * _charWidth + (_selection.x % 2) * _charWidth, _rowOffset.y + (_selection.y * _charHeight) - _charHeight + _charDescent, _charWidth, _charHeight);
		g.fillRect(_colOffset.x + 49 * _charWidth + (_selection.x / 2) * _charWidth, _rowOffset.y + (_selection.y * _charHeight) - _charHeight + _charDescent, _charWidth, _charHeight);
		g.setColor(Color.BLACK);
	}
	
	protected void paintString(Graphics g)
	{
		g.setFont(this.getFont());
		
		int x = _colOffset.x + 49 * _charWidth;
		int y = _rowOffset.y;
		int rows = this.getRowCount();
		
		for(int i = 0; i < rows; i++)
		{
			for(int j = 0; j < 16 && j + (i << 4) < _data.size(); j++)
			{
				g.drawString(Character.toString((char) _data.get(i * 16 + j).byteValue()), x, y);
				x += _charWidth;
			}
			x = _colOffset.x + 49 * _charWidth;
			y += _charHeight;
		}
		
		g.drawString("?", _colOffset.x + 49 * _charWidth + _data.size() % 16 * _charWidth, _rowOffset.y + _data.size() / 16 * _charHeight);
	}
	
	//Public Methoden
	
	public void clear()
	{
		_data.clear();
		this.updateDimension();
		this.repaint();
	}
	
	public byte[] getData()
	{
		int size = _data.size();
		byte[] data = new byte[size];
		for(int i = 0; i < size; i++)
		{
			data[i] = _data.get(i);
		}
		return data;
	}
	
	public static JScrollPane getModifiedScrollPane()
	{
		JScrollPane pane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		pane.getActionMap().put("unitScrollUp", new AbstractAction(){
		    @Override
		    public void actionPerformed(ActionEvent e) {
		    }});
		pane.getActionMap().put("unitScrollDown", new AbstractAction(){
		    @Override
		    public void actionPerformed(ActionEvent e) {
		    }});
		pane.getActionMap().put("unitScrollLeft", new AbstractAction(){
		    @Override
		    public void actionPerformed(ActionEvent e) {
		    }});
		pane.getActionMap().put("unitScrollRight", new AbstractAction(){
		    @Override
		    public void actionPerformed(ActionEvent e) {
		    }});
		return pane;
	}
	
	public void loadFromFile(File f) throws IOException
	{
		_data.clear();
		
		int data_read = -1;
		byte[] buffer = new byte[512];
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
		
		while((data_read = in.read(buffer)) != -1)
		{
			for(int i = 0; i < data_read; i++)
			{
				_data.add(buffer[i]);
			}
		}
		
		in.close();
		this.updateDimension();
		this.getParent().revalidate();
		this.repaint();
	}
	
	public void saveToFile(File f) throws IOException
	{
		BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f));
		for(int i = 0; i < _data.size(); i++)
		{
			out.write(_data.get(i));
		}
		out.close();
	}
	
	public void setData(byte[] data)
	{
		_data.clear();
		int size = data.length;
		for(int i = 0; i < size; i++)
		{
			_data.add(data[i]);
		}
		this.repaint();
	}
	
	@Override
	public void setFont(Font f)
	{
		super.setFont(f.deriveFont(Font.PLAIN));
		this.updateOffets();
		this.updateDimension();
		this.repaint();
	}

}
