import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SwingPresenter {
	private Main main;
	private JPanel mainPanel;
	private Map<String, Component> components = new HashMap<>();

	public SwingPresenter(Main main, JPanel mainPanel) {
		this.main = main;
		this.mainPanel = mainPanel;
	}

	public void screenUpdate(Map<String, Object> prevValue, Map<String, Object> value) {
		Map<String, Object> widgets = (Map<String, Object>) value.get("widgets");
		boolean layout = (boolean) value.get("layout");
		String prevScreenId = (String) prevValue.get("screenId");
		String screenId = (String) value.get("screenId");
		if (screenId.equals(prevScreenId)) {
			return;
		}
		mainPanel.removeAll();
		if (layout) {
			mainPanel.setLayout(new FlowLayout());
			for (String key : widgets.keySet()) {
				String type = (String) ((Map<String, Object>) widgets.get(key)).get("type");
				String text = (String) ((Map<String, Object>) widgets.get(key)).get("text");
				boolean visible = (boolean) ((Map<String, Object>) widgets.get(key)).get("visible");
				int state = (int) ((Map<String, Object>) widgets.get(key)).get("state");
				if (type.equals("textInput")) {
					JTextField textField = new JTextField(10);
					mainPanel.add(textField);
					components.put(key, textField);
					textField.getDocument().addDocumentListener(new DocumentListener() {
						@Override
						public void insertUpdate(DocumentEvent e) {
							Document d = e.getDocument();
							try {
								String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
							} catch (BadLocationException ex) {
								throw new RuntimeException(ex);
							}
						}
						@Override
						public void removeUpdate(DocumentEvent e) {
							Document d = e.getDocument();
							try {
								String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
							} catch (BadLocationException ex) {
								throw new RuntimeException(ex);
							}
						}
						@Override
						public void changedUpdate(DocumentEvent e) {
							Document d = e.getDocument();
							try {
								String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
							} catch (BadLocationException ex) {
								throw new RuntimeException(ex);
							}
						}
					});
				} else if (type.equals("label")) {
					JLabel label = new JLabel(text);
					mainPanel.add(label);
					components.put(key, label);
					label.addMouseListener(new MouseListener() {
						@Override
						public void mouseClicked(MouseEvent e) {
						}
						@Override
						public void mousePressed(MouseEvent e) {
							main.mouseEvent(key, 1);
						}
						@Override
						public void mouseReleased(MouseEvent e) {
							main.mouseEvent(key, 0);
						}
						@Override
						public void mouseEntered(MouseEvent e) {
						}
						@Override
						public void mouseExited(MouseEvent e) {
						}
					});
				} else if (type.equals("button")) {
					JButton button = new JButton(text);
					mainPanel.add(button);
					components.put(key, button);
					button.addMouseListener(new MouseListener() {
						@Override
						public void mouseClicked(MouseEvent e) {
						}
						@Override
						public void mousePressed(MouseEvent e) {
							main.mouseEvent(key, 1);
						}
						@Override
						public void mouseReleased(MouseEvent e) {
							main.mouseEvent(key, 0);
						}
						@Override
						public void mouseEntered(MouseEvent e) {
						}
						@Override
						public void mouseExited(MouseEvent e) {
						}
					});
				} else if (type.equals("table")) {
                    Map<String, Map<String, Object>> data = (Map<String, Map<String, Object>>) ((Map<String, Object>) widgets.get(key)).get("data");
                    List<String> columns = (List<String>) ((Map<String, Object>) widgets.get(key)).get("columns");
                    String primaryKeyName = (String) ((Map<String, Object>) widgets.get(key)).get("primaryKeyName");
                    boolean primaryKeyVisible = !primaryKeyName.equals("");
                    int colNum = columns.size() + (primaryKeyVisible ? 1 : 0);
                    String[] columnsData = new String[colNum];
                    String[][] tableData = new String[data.keySet().size()][colNum];
                    if(primaryKeyVisible) {
                        columnsData[0] = primaryKeyName;
                        for(int i = 1; i < colNum; i++) {
                            columnsData[i] = columns.get(i - 1);
                        }
                    } else {
                        for(int i = 0; i < colNum; i++) {
                            columnsData[i] = columns.get(i);
                        }
                    }
                    int rowCount = 0;
                    for(String dataKey : data.keySet()) {
                        Map<String, Object> rowData = data.get(dataKey);
                        if(primaryKeyVisible) {
                            tableData[rowCount][0] = dataKey;
                            for(int j = 1; j < columnsData.length; j++) {
                                Object cellValue = rowData.get(columnsData[j]);
                                if(cellValue == null) {
                                    tableData[rowCount][j] = "error";
                                } else {
                                    tableData[rowCount][j] = rowData.get(columnsData[j]).toString();
                                }
                            }
                        } else {
                            for(int j = 0; j < columnsData.length; j++) {
                                Object cellValue = rowData.get(columnsData[j]);
                                if(cellValue == null) {
                                    tableData[rowCount][j] = "error";
                                } else {
                                    tableData[rowCount][j] = rowData.get(columnsData[j]).toString();
                                }
                            }
                        }
                        rowCount++;
                    }
                    DefaultTableModel tableModel = new DefaultTableModel(tableData, columnsData);
                    JTable table = new JTable(tableModel) {
                        @Override
                        public boolean isCellEditable(int row, int col) {
                            return false;
                        }
                    };
                    JScrollPane scroll = new JScrollPane(table);
                    mainPanel.add(scroll);
                    components.put(key, scroll);
				}
			}
		} else {
			mainPanel.setLayout(null);
			for (String key : widgets.keySet()) {
				String type = (String) ((Map<String, Object>) widgets.get(key)).get("type");
				int y = (int) ((Map<String, Object>) widgets.get(key)).get("y");
				int x = (int) ((Map<String, Object>) widgets.get(key)).get("x");
				int height = (int) ((Map<String, Object>) widgets.get(key)).get("height");
				String text = (String) ((Map<String, Object>) widgets.get(key)).get("text");
				boolean visible = (boolean) ((Map<String, Object>) widgets.get(key)).get("visible");
				int width = (int) ((Map<String, Object>) widgets.get(key)).get("width");
				int state = (int) ((Map<String, Object>) widgets.get(key)).get("state");
				if (type.equals("textInput")) {
					JTextField textField = new JTextField(10);
					textField.setLocation(x, y);
					textField.setSize(width, height);
					mainPanel.add(textField);
					components.put(key, textField);
					textField.getDocument().addDocumentListener(new DocumentListener() {
						@Override
						public void insertUpdate(DocumentEvent e) {
							Document d = e.getDocument();
                            try {
                                String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
                            } catch (BadLocationException ex) {
                                throw new RuntimeException(ex);
                            }
                        }
						@Override
						public void removeUpdate(DocumentEvent e) {
							Document d = e.getDocument();
							try {
								String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
							} catch (BadLocationException ex) {
								throw new RuntimeException(ex);
							}
						}
						@Override
						public void changedUpdate(DocumentEvent e) {
							Document d = e.getDocument();
							try {
								String text = d.getText(0, d.getLength());
								main.textEvent(key, text);
							} catch (BadLocationException ex) {
								throw new RuntimeException(ex);
							}
						}
					});
				} else if (type.equals("label")) {
					JLabel label = new JLabel(text);
					label.setLocation(x, y);
					label.setSize(width, height);
					mainPanel.add(label);
					components.put(key, label);
					label.addMouseListener(new MouseListener() {
						@Override
						public void mouseClicked(MouseEvent e) {
						}
						@Override
						public void mousePressed(MouseEvent e) {
							main.mouseEvent(key, 1);
						}
						@Override
						public void mouseReleased(MouseEvent e) {
							main.mouseEvent(key, 0);
						}
						@Override
						public void mouseEntered(MouseEvent e) {
						}
						@Override
						public void mouseExited(MouseEvent e) {
						}
					});
				} else if (type.equals("button")) {
					JButton button = new JButton(text);
					button.setLocation(x, y);
					button.setSize(width, height);
					mainPanel.add(button);
					components.put(key, button);
					button.addMouseListener(new MouseListener() {
						@Override
						public void mouseClicked(MouseEvent e) {
						}
						@Override
						public void mousePressed(MouseEvent e) {
							main.mouseEvent(key, 1);
						}
						@Override
						public void mouseReleased(MouseEvent e) {
							main.mouseEvent(key, 0);
						}
						@Override
						public void mouseEntered(MouseEvent e) {
						}
						@Override
						public void mouseExited(MouseEvent e) {
						}
					});
				} else if (type.equals("table")) {
                    Map<String, Map<String, Object>> data = (Map<String, Map<String, Object>>) ((Map<String, Object>) widgets.get(key)).get("data");
                    List<String> columns = (List<String>) ((Map<String, Object>) widgets.get(key)).get("columns");
                    String primaryKeyName = (String) ((Map<String, Object>) widgets.get(key)).get("primaryKeyName");
                    boolean primaryKeyVisible = !primaryKeyName.equals("");
                    int colNum = columns.size() + (primaryKeyVisible ? 1 : 0);
                    String[] columnsData = new String[colNum];
                    String[][] tableData = new String[data.keySet().size()][colNum];
                    if(primaryKeyVisible) {
                        columnsData[0] = primaryKeyName;
                        for(int i = 1; i < colNum; i++) {
                            columnsData[i] = columns.get(i - 1);
                        }
                    } else {
                        for(int i = 0; i < colNum; i++) {
                            columnsData[i] = columns.get(i);
                        }
                    }
                    int rowCount = 0;
                    for(String dataKey : data.keySet()) {
                        Map<String, Object> rowData = data.get(dataKey);
                        if(primaryKeyVisible) {
                            tableData[rowCount][0] = dataKey;
                            for(int j = 1; j < columnsData.length; j++) {
                                Object cellValue = rowData.get(columnsData[j]);
                                if(cellValue == null) {
                                    tableData[rowCount][j] = "error";
                                } else {
                                    tableData[rowCount][j] = rowData.get(columnsData[j]).toString();
                                }
                            }
                        } else {
                            for(int j = 0; j < columnsData.length; j++) {
                                Object cellValue = rowData.get(columnsData[j]);
                                if(cellValue == null) {
                                    tableData[rowCount][j] = "error";
                                } else {
                                    tableData[rowCount][j] = rowData.get(columnsData[j]).toString();
                                }
                            }
                        }
                        rowCount++;
                    }
                    DefaultTableModel tableModel = new DefaultTableModel(tableData, columnsData);
                    JTable table = new JTable(tableModel) {
                        @Override
                        public boolean isCellEditable(int row, int col) {
                            return false;
                        }
                    };
                    JScrollPane scroll = new JScrollPane(table);
                    scroll.setLocation(x, y);
                    scroll.setSize(width, height);
                    mainPanel.add(scroll);
                    components.put(key, scroll);
				}
			}
		}
		mainPanel.invalidate();
		mainPanel.validate();
		mainPanel.repaint();
	}

	public void setX(String wid, int x) {
		int y = components.get(wid).getY();
		components.get(wid).setLocation(x, y);
	}

	public void setY(String wid, int y) {
		int x = components.get(wid).getX();
		components.get(wid).setLocation(x, y);
	}

	public void setText(String wid, String text) {
		Component widget = components.get(wid);
		if (widget instanceof JButton) {
			((JButton) widget).setText(text);
		} else if (widget instanceof JLabel) {
			((JLabel) widget).setText(text);
		} else if (widget instanceof JTextField) {
			((JTextField) widget).setText(text);
		}
	}

	public void setVisible(String wid, boolean visible) {
		components.get(wid).setVisible(visible);
	}

	public void setTable(String scId, String wid, Map<String, Map<String, Object>> data) {
	}

	public void setLayout(boolean layout) {
		if (layout) {
			mainPanel.setLayout(new FlowLayout());
		} else {
			mainPanel.setLayout(null);
		}
	}
}
