package hr.fer.zemris.ffr.desktop.formCreator;

import hr.fer.zemris.ffr.desktop.formCreator.model.PMProject;

import hr.fer.zemris.ffr.desktop.formCreator.model.PMRoom;
import hr.fer.zemris.ffr.desktop.formCreator.model.PMStudent;
import hr.fer.zemris.jcms.model.Assessment;
import hr.fer.zemris.jcms.model.AssessmentConfChoice;
import hr.fer.zemris.jcms.model.AssessmentRoom;
import hr.fer.zemris.jcms.model.Course;
import hr.fer.zemris.jcms.model.CourseInstance;
import hr.fer.zemris.jcms.model.CourseWideEvent;
import hr.fer.zemris.jcms.model.Group;
import hr.fer.zemris.jcms.model.Room;
import hr.fer.zemris.jcms.model.User;
import hr.fer.zemris.jcms.model.UserGroup;
import hr.fer.zemris.jcms.model.YearSemester;
import hr.fer.zemris.jcms.service.assessments.FormGenerator;
import hr.fer.zemris.jcms.service.assessments.ListsGenerator;
import hr.fer.zemris.util.StringUtil;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.AbstractTableModel;

public class ProjectGUI extends JInternalFrame {

	private static final long serialVersionUID = 1L;
	private GUI gui;
	private static int counter = 1;
	private File projectFileName;
	private JLabel projectFileLabel;
	private JLabel projectFileLabelLabel;
	private PMProject model;
	private JTabbedPane tabbedPane;
	private StudentsTableModel studentsTableModel;
	private RoomsTableModel roomsTableModel;
	private StudentScheduleTableModel studentScheduleTableModel;
	private GeneralDataTableModel generalDataTableModel;
	private Preferences prefs;
	private JPanel problemLabelsPanel;
	private JPanel groupLabelsPanel;
	private JPanel answerLabelsPanel;
	private AnswersTableModel answersTableModel;
	private ProblemsTableModel problemsTableModel;
	private GroupsTableModel groupsTableModel;
	
	private JLabel roomsInfoLbl;
	
	public ProjectGUI(GUI gui) {
		super("Projekt #"+counter, true, true, true, true);
		counter++;
		this.gui = gui;
		this.model = new PMProject();
		prefs = Preferences.userNodeForPackage(ProjectGUI.class);
		gui.internalFramesList.add(this);
		setSize(600, 400);
		setLocation(((counter-1)%10)*30, ((counter-1)%10)*30);
		addInternalFrameListener(new InternalFrameAdapter() {
			@Override
			public void internalFrameClosing(InternalFrameEvent e) {
				handleInternalFrameClosing();
			}
			@Override
			public void internalFrameClosed(InternalFrameEvent e) {
				handleInternalFrameClosed();
			}
		});
		JPanel topPanel = new JPanel(new BorderLayout());
		projectFileLabel = new JLabel();
		projectFileLabelLabel = new JLabel();
		if(projectFileName!=null) {
			projectFileLabel.setText(projectFileName.getAbsolutePath());
		}
		topPanel.add(projectFileLabelLabel, BorderLayout.LINE_START);
		topPanel.add(projectFileLabel, BorderLayout.CENTER);
		this.getContentPane().add(topPanel, BorderLayout.PAGE_START);
		
		JPanel bottomPanel = new JPanel(new GridLayout(1,0));
		JButton btStart = new JButton(gui.actionMap.get("act.produceForms"));
		bottomPanel.add(btStart);
		JButton btStartS = new JButton(gui.actionMap.get("act.produceLists"));
		bottomPanel.add(btStartS);
		this.getContentPane().add(bottomPanel, BorderLayout.PAGE_END);
		
		tabbedPane = new JTabbedPane();
		this.getContentPane().add(tabbedPane, BorderLayout.CENTER);

		JPanel opciPodatci = createGeneralData();
		tabbedPane.addTab(ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("lab.generalTab"), opciPodatci);
		
		JPanel studenti = createStudentsPanel();
		tabbedPane.addTab(ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("lab.studentsTab"), studenti);
		
		JPanel rooms = createRoomsPanel();
		tabbedPane.addTab(ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("lab.roomsTab"), rooms);

		JPanel studentSchedule = createStudentSchedulePanel();
		tabbedPane.addTab(ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("lab.studentScheduleTab"), studentSchedule);

		adjustLanguage();
	}

	public File getProjectFileName() {
		return projectFileName;
	}
	
	public void setProjectFileName(File projectFileName) {
		this.projectFileName = projectFileName;
		if(projectFileName!=null) {
			projectFileLabel.setText(projectFileName.getAbsolutePath());
		} else {
			projectFileLabel.setText("");
		}
	}
	
	private JPanel createGeneralData() {
		JPanel panel = new JPanel(new BorderLayout());
		
		generalDataTableModel = new GeneralDataTableModel();
		JTable table = new JTable(generalDataTableModel);
		JPanel bottomPanel = new JPanel(new GridLayout(1,2));
		JScrollPane sp = new JScrollPane(table);
		sp.setPreferredSize(new Dimension(300,200));
		sp.setMinimumSize(new Dimension(300,100));
		JSplitPane jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sp, bottomPanel);
		panel.add(jsp, BorderLayout.CENTER);
		JPanel leftPanel = new JPanel(new GridLayout(2,1));
		JPanel rightPanel = createProblemLabelsPanel();
		bottomPanel.add(leftPanel);
		bottomPanel.add(rightPanel);
		JPanel leftUp = createGroupLabelsPanel();
		JPanel leftDown = createAnswerLabelsPanel();
		leftPanel.add(leftUp);
		leftPanel.add(leftDown);
		return panel;
	}

	private class ProblemsTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			return rb.getString("table.Label");
		}

		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return columnIndex==1;
		}
		
		@Override
		public int getColumnCount() {
			return 2;
		}
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			return String.class;
		}
		
		@Override
		public int getRowCount() {
			return model.getProblemLabels()==null ? 0 : model.getProblemLabels().length;
		}
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) return Integer.valueOf(rowIndex+1);
			return model.getProblemLabels()[rowIndex];
		}
		
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			if(columnIndex==1) {
				model.getProblemLabels()[rowIndex] = (String)aValue;
				fireTableCellUpdated(rowIndex, columnIndex);
			}
		}
	}
	
	private JPanel createProblemLabelsPanel() {
		JPanel panel = new JPanel(new BorderLayout());
		problemLabelsPanel = panel;
		panel.setBorder(BorderFactory.createTitledBorder("X"));
		problemsTableModel = new ProblemsTableModel();
		JTable table = new JTable(problemsTableModel);
		panel.add(new JScrollPane(table), BorderLayout.CENTER);
		JButton b = new JButton(gui.actionMap.get("act.problems.set1"));
		panel.add(b, BorderLayout.PAGE_END);
		return panel;
	}

	private class AnswersTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		private String[] elems;
		
		private void checkElems() {
			if(elems==null || elems.length!=model.getAnswersCount()) {
				elems = new String[model.getAnswersCount()];
				for(int i = 0; i < elems.length; i++) {
					elems[i] = Character.valueOf((char)('A'+i)).toString();
				}
			}
		}

		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			return rb.getString("table.Label");
		}

		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return false;
		}
		
		@Override
		public int getColumnCount() {
			return 2;
		}
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			return String.class;
		}
		
		@Override
		public int getRowCount() {
			checkElems();
			return elems.length;
		}
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) return Integer.valueOf(rowIndex+1);
			return elems[rowIndex];
		}
	}
	
	private JPanel createAnswerLabelsPanel() {
		JPanel panel = new JPanel(new BorderLayout());
		answerLabelsPanel = panel;
		panel.setBorder(BorderFactory.createTitledBorder("X"));
		answersTableModel = new AnswersTableModel();
		JTable table = new JTable(answersTableModel);
		panel.add(new JScrollPane(table), BorderLayout.CENTER);
		JButton b = new JButton(gui.actionMap.get("act.answers.set1"));
		panel.add(b, BorderLayout.PAGE_END);
		return panel;
	}

	private class GroupsTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			return rb.getString("table.Label");
		}

		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return model.getGroupLabels()!=null && model.getGroupLabels().length<=5 && columnIndex==1;
		}
		
		@Override
		public int getColumnCount() {
			return 2;
		}
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			return String.class;
		}
		
		@Override
		public int getRowCount() {
			return model.getGroupLabels()==null ? 0 : model.getGroupLabels().length;
		}
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) return Integer.valueOf(rowIndex+1);
			return model.getGroupLabels()[rowIndex];
		}
		
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			if(columnIndex==1) {
				model.getGroupLabels()[rowIndex] = (String)aValue;
				fireTableCellUpdated(rowIndex, columnIndex);
			}
		}
	}
	
	private JPanel createGroupLabelsPanel() {
		JPanel panel = new JPanel(new BorderLayout());
		groupLabelsPanel = panel;
		panel.setBorder(BorderFactory.createTitledBorder("X"));
		groupsTableModel = new GroupsTableModel();
		JTable table = new JTable(groupsTableModel);
		panel.add(new JScrollPane(table), BorderLayout.CENTER);
		JButton b = new JButton(gui.actionMap.get("act.groups.set1"));
		panel.add(b, BorderLayout.PAGE_END);
		return panel;
	}

	private JPanel createStudentSchedulePanel() {
		JPanel panel = new JPanel(new BorderLayout());
		JPanel topPanel = new JPanel(new GridLayout(1,0));
		JButton razmjestiAbecedno = new JButton(gui.actionMap.get("act.schedule.alphabetical"));
		JButton razmjestiSlucajno = new JButton(gui.actionMap.get("act.schedule.random"));
		JButton ucitajMM = new JButton(gui.actionMap.get("act.schedule.load1"));
		topPanel.add(razmjestiAbecedno);
		topPanel.add(razmjestiSlucajno);
		topPanel.add(ucitajMM);
		studentScheduleTableModel = new StudentScheduleTableModel();
		JTable table = new JTable(studentScheduleTableModel);
		panel.add(topPanel, BorderLayout.PAGE_START);
		panel.add(new JScrollPane(table), BorderLayout.CENTER);
		return panel;
	}

	private JPanel createStudentsPanel() {
		JPanel studenti = new JPanel(new BorderLayout());
		JPanel topPanel = new JPanel(new GridLayout(1,0));
		JButton dodajStudente = new JButton(gui.actionMap.get("act.students.load1"));
		topPanel.add(dodajStudente);
		studentsTableModel = new StudentsTableModel();
		studentsTableModel.addTableModelListener(new TableModelListener() {
			public void tableChanged(javax.swing.event.TableModelEvent e) {
				adjustRoomInfoStatus();
			};
		});
		JTable table = new JTable(studentsTableModel);
		studenti.add(topPanel, BorderLayout.PAGE_START);
		studenti.add(new JScrollPane(table), BorderLayout.CENTER);
		return studenti;
	}

	private JPanel createRoomsPanel() {
		JPanel sobe = new JPanel(new BorderLayout());
		JPanel topPanel = new JPanel(new GridLayout(1,0));
		JButton dodajSobe = new JButton(gui.actionMap.get("act.rooms.load1"));
		topPanel.add(dodajSobe);
		roomsTableModel = new RoomsTableModel();
		JTable table = new JTable(roomsTableModel);
		JPanel bottomPanel = new JPanel(new BorderLayout());
		roomsInfoLbl = new JLabel("0/0");
		bottomPanel.add(roomsInfoLbl, BorderLayout.CENTER);
		sobe.add(topPanel, BorderLayout.PAGE_START);
		sobe.add(new JScrollPane(table), BorderLayout.CENTER);
		sobe.add(bottomPanel, BorderLayout.PAGE_END);
		roomsTableModel.addTableModelListener(new TableModelListener() {
			public void tableChanged(javax.swing.event.TableModelEvent e) {
				adjustRoomInfoStatus();
			};
		});
		return sobe;
	}

	private void adjustRoomInfoStatus() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		int roomsCount = 0;
		int capacity = 0;
		for(PMRoom r : model.getRooms()) {
			if(r.isTaken()) {
				roomsCount++;
				capacity += r.getCapacity();
			}
		}
		StringBuilder sb = new StringBuilder();
		sb.append(roomsCount).append("/").append(model.getRooms().size()).append(rb.getString("info1.1"));
		int visak = capacity - model.getStudents().size();
		if(visak>0) {
			sb.append(visak).append(rb.getString("info1.2"));
			roomsInfoLbl.setOpaque(false);
		} else if(visak<0) {
			sb.append(-visak).append(rb.getString("info1.3"));
			roomsInfoLbl.setOpaque(true);
			roomsInfoLbl.setBackground(Color.RED);
		} else {
			sb.append(rb.getString("info1.4"));
			roomsInfoLbl.setOpaque(false);
		}
		roomsInfoLbl.setText(sb.toString());
	}

	protected void alphabeticalStudentSchedule() {
		List<PMStudent> students = new ArrayList<PMStudent>(model.getStudents());
		Collections.sort(students);
		createSchedule(students);
	}

	protected void randomStudentSchedule() {
		List<PMStudent> students = new ArrayList<PMStudent>(model.getStudents());
		Collections.shuffle(students);
		createSchedule(students);
	}

	private void createSchedule(List<PMStudent> students) {
		if(students.size()<1) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			JOptionPane.showMessageDialog(gui, rb.getString("err.missingStudents"));
			return;
		}
		int capacity = 0;
		List<PMRoom> takenRooms = new ArrayList<PMRoom>(model.getRooms().size());
		for(PMRoom r : model.getRooms()) {
			if(r.isTaken()) {
				takenRooms.add(r);
				capacity += r.getCapacity();
			}
		}
		if(capacity < students.size()) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			JOptionPane.showMessageDialog(gui, rb.getString("err.notEnoughPlaces"));
			return;
		}
		Collections.sort(takenRooms);
		int[] howMuch = new int[takenRooms.size()];
		int takenCapacity = 0;
		for(int i = 0; i < howMuch.length; i++) {
			howMuch[i] = (int)((double)students.size()/(double)capacity*takenRooms.get(i).getCapacity()+0.5);
			takenCapacity += howMuch[i];
		}
		if(takenCapacity>students.size()) {
			int visak = takenCapacity - students.size();
			while(visak>0) {
				for(int i = howMuch.length-1; i >= 0 && visak>0; i--) {
					if(howMuch[i]>0) {
						howMuch[i]--;
						visak--;
					}
				}
			}
		}
		while(true) {
			int cap = 0;
			for(int i = 0; i < howMuch.length; i++) {
				cap += howMuch[i];
			}
			if(cap>=students.size()) break;
			int nedostaje = students.size()-cap;
			for(int i = 0; i < howMuch.length && nedostaje>0; i++) {
				if(howMuch[i] < takenRooms.get(i).getCapacity()) {
					howMuch[i]++;
					nedostaje--;
				}
			}
			if(nedostaje<=0) break;
		}
		model.getStudentSchedule().clear();
		model.incrementStudentScheduleVersion();
		int fromIndex = 0;
		for(int i = 0; i < howMuch.length; i++) {
			PMRoom r = takenRooms.get(i);
			List<PMStudent> list = new ArrayList<PMStudent>(howMuch[i]);
			list.addAll(students.subList(fromIndex, fromIndex+howMuch[i]));
			fromIndex += howMuch[i];
			model.getStudentSchedule().put(r, list);
		}
		if(fromIndex!=students.size()) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			JOptionPane.showMessageDialog(gui, rb.getString("err.unsuccessfulScheduling"));
		}
		studentScheduleTableModel.fireTableDataChanged();
	}

	private class GeneralDataTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return String.class;
			if(columnIndex==1) return String.class;
			return String.class;
		}
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.propertyName");
			if(column==1) return rb.getString("table.propertyValue");
			return "";
		}
		public int getColumnCount() { return 2; };
		@Override
		public int getRowCount() {
			return 7;
		}
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return columnIndex==1;
		}
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) {
				ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
				if(rowIndex==0) return rb.getString("table.courseName");
				if(rowIndex==1) return rb.getString("table.courseCode");
				if(rowIndex==2) return rb.getString("table.academicYear");
				if(rowIndex==3) return rb.getString("table.semester");
				if(rowIndex==4) return rb.getString("table.assessmentName");
				if(rowIndex==5) return rb.getString("table.date");
				if(rowIndex==6) return rb.getString("table.errorColumnLabel");
			}
			if(columnIndex==1) {
				if(rowIndex==0) return denull(model.getCourseName());
				if(rowIndex==1) return denull(model.getCourseISVU());
				if(rowIndex==2) return denull(model.getAcademicYear());
				if(rowIndex==3) return denull(model.getSemester());
				if(rowIndex==4) return denull(model.getAssessmentName());
				if(rowIndex==5) return model.getDate()==null ? "" : sdf.format(model.getDate());
				if(rowIndex==6) return model.getErrorColumn();
			}
			return "";
		}
		
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			String value = ((String)aValue).trim();
			switch(rowIndex) {
			case 0: 
				model.setCourseName(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 1: 
				model.setCourseISVU(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 2: 
				if(!checkAcademicYear(value)) break;
				model.setAcademicYear(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 3: 
				model.setSemester(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 4: 
				model.setAssessmentName(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 5: 
				if(!checkDate(value)) break;
				if(StringUtil.isStringBlank(value)) {
					model.setDate(null);
				} else {
					try {
						model.setDate(sdf.parse(value));
					} catch(Exception ex) {
						model.setDate(null);
					}
				}
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			case 6: 
				model.setErrorColumn(value);
				this.fireTableCellUpdated(rowIndex, columnIndex);
				break;
			}
		}
		
		private boolean checkDate(String value) {
			if(StringUtil.isStringBlank(value)) return true;
			if(!StringUtil.checkStandardDateFormat(value)) {
				ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
				JOptionPane.showInternalMessageDialog(ProjectGUI.this, rb.getString("err.missformattedDate"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
				return false;
			}
			return true;
		}
		private boolean checkAcademicYear(String aValue) {
			if(aValue.isEmpty()) return true;
			try {
				if(aValue.length()!=9) throw new Exception();
				int a = Integer.parseInt(aValue.substring(0,4));
				int b = Integer.parseInt(aValue.substring(5,9));
				if(a+1!=b) throw new Exception();
				if(aValue.charAt(4)!='/') throw new Exception();
			} catch(Exception ex) {
				ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
				JOptionPane.showInternalMessageDialog(ProjectGUI.this, rb.getString("err.missformattedAcademicYear"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
				return false;
			}
			return true;
		}
	}
	
	private class StudentsTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			if(columnIndex==1) return String.class;
			if(columnIndex==2) return String.class;
			if(columnIndex==3) return String.class;
			return String.class;
		}
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			if(column==1) return rb.getString("table.StudentIdentifier");
			if(column==2) return rb.getString("table.LastName");
			if(column==3) return rb.getString("table.FirstName");
			return "";
		}
		public int getColumnCount() { return 4; };
		@Override
		public int getRowCount() {
			return model.getStudents().size();
		}
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) return Integer.toString(rowIndex+1);
			if(columnIndex==1) return model.getStudents().get(rowIndex).getJmbag();
			if(columnIndex==2) return model.getStudents().get(rowIndex).getLastName();
			if(columnIndex==3) return model.getStudents().get(rowIndex).getFirstName();
			return "";
		}
	}
	
	private class RoomsTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			if(columnIndex==1) return String.class;
			if(columnIndex==2) return Integer.class;
			if(columnIndex==3) return Boolean.class;
			return String.class;
		}
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			if(column==1) return rb.getString("table.RoomName");
			if(column==2) return rb.getString("table.Capacity");
			if(column==3) return rb.getString("table.Taken");
			return "";
		}
		public int getColumnCount() { return 4; };
		@Override
		public int getRowCount() {
			return model.getRooms().size();
		}
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return columnIndex>=2;
		}
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			if(columnIndex==0) return Integer.toString(rowIndex+1);
			if(columnIndex==1) return model.getRooms().get(rowIndex).getName();
			if(columnIndex==2) return Integer.valueOf(model.getRooms().get(rowIndex).getCapacity());
			if(columnIndex==3) return model.getRooms().get(rowIndex).isTaken() ? Boolean.TRUE : Boolean.FALSE;
			return "";
		}
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			if(columnIndex==2) {
				if(!updateAllowed()) return;
				model.getRooms().get(rowIndex).setCapacity(((Integer)aValue).intValue());
				model.incrementStudentScheduleVersion();
				model.getStudentSchedule().clear();
				this.fireTableCellUpdated(rowIndex, columnIndex);
				studentScheduleTableModel.fireTableDataChanged();
			}
			if(columnIndex==3) {
				if(!updateAllowed()) return;
				model.getRooms().get(rowIndex).setTaken(((Boolean)aValue).booleanValue());
				model.incrementStudentScheduleVersion();
				model.getStudentSchedule().clear();
				this.fireTableCellUpdated(rowIndex, columnIndex);
				studentScheduleTableModel.fireTableDataChanged();
			}
		}
	}

	private class StudentScheduleTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 1L;
		
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if(columnIndex==0) return Integer.class;
			if(columnIndex==1) return String.class;
			if(columnIndex==2) return Integer.class;
			return String.class;
		}
		@Override
		public String getColumnName(int column) {
			ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
			if(column==0) return rb.getString("table.Number");
			if(column==1) return rb.getString("table.RoomName");
			if(column==2) return rb.getString("table.Students");
			return "";
		}
		public int getColumnCount() { return 3; };
		@Override
		public int getRowCount() {
			checkModel();
			return roomsList.size();
		}
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return false;
		}
		
		private List<PMRoom> roomsList;
		private long studentScheduleVersion;

		private void checkModel() {
			if(roomsList==null || studentScheduleVersion!=model.getStudentScheduleVersion()) {
				studentScheduleVersion = model.getStudentScheduleVersion();
				roomsList = new ArrayList<PMRoom>(model.getStudentSchedule().keySet());
				Collections.sort(roomsList);
			}
		}
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			checkModel();
			if(columnIndex==0) return Integer.toString(rowIndex+1);
			if(columnIndex==1) return roomsList.get(rowIndex).getName();
			if(columnIndex==2) return Integer.valueOf(model.getStudentSchedule().get(roomsList.get(rowIndex)).size());
			return "";
		}
	}

	public void adjustLanguage() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		tabbedPane.setTitleAt(0, rb.getString("lab.generalTab"));
		tabbedPane.setTitleAt(1, rb.getString("lab.studentsTab"));
		tabbedPane.setTitleAt(2, rb.getString("lab.roomsTab"));
		tabbedPane.setTitleAt(3, rb.getString("lab.studentScheduleTab"));
		projectFileLabelLabel.setText(rb.getString("lab.projectName"));
		generalDataTableModel.fireTableStructureChanged();
		studentsTableModel.fireTableStructureChanged();
		roomsTableModel.fireTableStructureChanged();
		studentScheduleTableModel.fireTableStructureChanged();
		answersTableModel.fireTableStructureChanged();
		groupsTableModel.fireTableStructureChanged();
		problemsTableModel.fireTableStructureChanged();
		problemLabelsPanel.setBorder(BorderFactory.createTitledBorder(rb.getString("title.problemLabels")));
		groupLabelsPanel.setBorder(BorderFactory.createTitledBorder(rb.getString("title.groupLabels")));
		answerLabelsPanel.setBorder(BorderFactory.createTitledBorder(rb.getString("title.answerLabels")));
	}
	
	public String denull(String text) {
		return text==null ? "" : text;
	}

	public boolean updateAllowed() {
		if(model.getStudentSchedule().isEmpty()) return true;
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		if(JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(gui, rb.getString("warn.cancelSchedule"), rb.getString("err.systemMessage"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) {
			return false;
		}
		return true;
	}

	public void closeFrame() {
		handleInternalFrameClosing();
		if(isVisible()) return;
		handleInternalFrameClosed();
	}
	
	protected void handleInternalFrameClosing() {
		setVisible(false);
	}

	protected void handleInternalFrameClosed() {
		gui.internalFramesList.remove(this);
	}

	public void producePDF() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		if(!isModelReady()) {
			JOptionPane.showInternalMessageDialog(this, rb.getString("err.missingData"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
			return;
		}
		if(model.getStudentSchedule().isEmpty()) {
			JOptionPane.showInternalMessageDialog(this, rb.getString("err.missingSchedule"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectPDFToCreate"));
		if(JFileChooser.APPROVE_OPTION != fc.showSaveDialog(this)) {
			return;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());

		Assessment a = buildAssessment();
		File pdfLocation = fc.getSelectedFile();
		BufferedOutputStream bos = null;
		boolean err = false;
		try {
			bos = new BufferedOutputStream(new FileOutputStream(pdfLocation));
			FormGenerator fg = new FormGenerator(bos);
			fg.generateForms(a, a);
			fg.close();
			bos.close();
			bos = null;
		} catch(Exception ex) {
			ex.printStackTrace();
			err = true;
			JOptionPane.showMessageDialog(gui, ex.getMessage());
		} finally {
			if(bos!=null) try { bos.close(); } catch(Exception ex) {}
		}
		if(!err) {
			JOptionPane.showMessageDialog(gui, rb.getString("dialog.PDFcreated"), rb.getString("err.systemMessage"), JOptionPane.INFORMATION_MESSAGE);
		}
	}

	public void produceSchedulePDF() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		if(!isModelReady()) {
			JOptionPane.showInternalMessageDialog(this, rb.getString("err.missingData"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
			return;
		}
		if(model.getStudentSchedule().isEmpty()) {
			JOptionPane.showInternalMessageDialog(this, rb.getString("err.missingSchedule"), rb.getString("err.systemMessage"), JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectPDFToCreate"));
		if(JFileChooser.APPROVE_OPTION != fc.showSaveDialog(this)) {
			return;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
		
		Assessment a = buildAssessment();
		File pdfLocation = fc.getSelectedFile();
		BufferedOutputStream bos = null;
		boolean err = false;
		try {
			bos = new BufferedOutputStream(new FileOutputStream(pdfLocation));
			ListsGenerator fg = new ListsGenerator(bos);
			fg.generateLists(a);
			fg.close();
			bos.close();
			bos = null;
		} catch(Exception ex) {
			err = true;
			JOptionPane.showMessageDialog(gui, ex.getMessage());
		} finally {
			if(bos!=null) try { bos.close(); } catch(Exception ex) {}
		}
		if(!err) {
			JOptionPane.showMessageDialog(gui, rb.getString("dialog.PDFcreated"), rb.getString("err.systemMessage"), JOptionPane.INFORMATION_MESSAGE);
		}
	}

	public void studentsLoad1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectStudentsFile"));
		if(JFileChooser.APPROVE_OPTION != fc.showOpenDialog(this)) {
			return;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
		Map<String,PMStudent> students = new LinkedHashMap<String, PMStudent>(1000);
		File f = fc.getSelectedFile();
		BufferedReader br = null;
		boolean err = false;
		ArrayList<PMStudent> duplicates = new ArrayList<PMStudent>();
		try {
			br = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(f)),"UTF-8"));
			boolean prvi = true;
			while(true) {
				String line = br.readLine();
				if(line==null) break;
				if(line.isEmpty()) continue;
				if(prvi) {
					prvi = false;
					if(line.length()<3) continue;
					line = deutf8bom(line);
				}
				int pos = line.indexOf('\t');
				String[] elems = line.split(pos!=-1 ? "\t" : ",");
				if(elems.length!=3) {
					JOptionPane.showMessageDialog(gui, ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("err.illegalFileRow")+line);
					err = true;
					break;
				}
				PMStudent s = students.get(elems[0]);
				if(s!=null) {
					duplicates.add(s);
					continue;
				}
				s = new PMStudent(elems[0], elems[1], elems[2]);
				students.put(elems[0], s);
			}
			br.close();
			br = null;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(gui, ex.getMessage());
			err = true;
		} finally {
			if(br!=null) try { br.close(); } catch(Exception ex) {}
		}
		if(err) return;
		List<PMStudent> list = new ArrayList<PMStudent>(students.size());
		for(Map.Entry<String,PMStudent> e : students.entrySet()) {
			list.add(e.getValue());
		}
		Collections.sort(list);
		model.getStudents().clear();
		model.getStudents().addAll(list);
		model.getStudentSchedule().clear();
		model.incrementStudentScheduleVersion();
		studentsTableModel.fireTableDataChanged();
		studentScheduleTableModel.fireTableDataChanged();
		
	}

	public void roomsLoad1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectRoomsFile"));
		if(JFileChooser.APPROVE_OPTION != fc.showOpenDialog(this)) {
			return;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
		Map<String,PMRoom> rooms = new LinkedHashMap<String, PMRoom>(100);
		File f = fc.getSelectedFile();
		BufferedReader br = null;
		boolean err = false;
		ArrayList<PMRoom> duplicates = new ArrayList<PMRoom>();
		try {
			br = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(f)),"UTF-8"));
			boolean prvi = true;
			while(true) {
				String line = br.readLine();
				if(line==null) break;
				if(line.isEmpty()) continue;
				if(prvi) {
					prvi = false;
					if(line.length()<3) continue;
					line = deutf8bom(line);
				}
				int pos = line.indexOf('\t');
				String[] elems = line.split(pos!=-1 ? "\t" : ",");
				if(elems.length!=2) {
					JOptionPane.showMessageDialog(gui, ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("err.illegalFileRow")+line);
					err = true;
					break;
				}
				PMRoom s = rooms.get(elems[0]);
				if(s!=null) {
					duplicates.add(s);
					continue;
				}
				s = new PMRoom(elems[0], Integer.parseInt(elems[1]));
				rooms.put(elems[0], s);
			}
			br.close();
			br = null;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(gui, ex.getMessage());
			err = true;
		} finally {
			if(br!=null) try { br.close(); } catch(Exception ex) {}
		}
		if(err) return;
		List<PMRoom> list = new ArrayList<PMRoom>(rooms.size());
		for(Map.Entry<String,PMRoom> e : rooms.entrySet()) {
			list.add(e.getValue());
		}
		Collections.sort(list);
		model.getRooms().clear();
		model.getRooms().addAll(list);
		model.getStudentSchedule().clear();
		model.incrementStudentScheduleVersion();
		roomsTableModel.fireTableDataChanged();
		studentScheduleTableModel.fireTableDataChanged();
	}

	public void answersSet1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JPanel p = new JPanel(new GridLayout(1,2));
		int ac = model.getAnswersCount();
		if(ac<2) ac = 6;
		SpinnerNumberModel spModel = new SpinnerNumberModel(ac, 2, 27, 1);
		JSpinner sp = new JSpinner(spModel);
		p.add(new JLabel(rb.getString("dialog.numberOfAnswers")));
		p.add(sp);
		if(JOptionPane.OK_OPTION!=JOptionPane.showInternalConfirmDialog(this, p, rb.getString("dialog.selectNumberOfAnswers"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) {
			return;
		}
		model.setAnswersCount(spModel.getNumber().intValue());
		answersTableModel.fireTableDataChanged();
	}
	
	public void problemsSet1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JPanel p = new JPanel(new GridLayout(2,2));
		int ac = model.getProblemLabels()==null ? 0 : model.getProblemLabels().length;
		if(ac<1) ac = 10;
		SpinnerNumberModel spModel = new SpinnerNumberModel(ac, 1, 500, 1);
		JSpinner sp = new JSpinner(spModel);
		p.add(new JLabel(rb.getString("dialog.problemMask")));
		JTextField tf = new JTextField(rb.getString("dialog.problemStem")+" #.");
		p.add(tf);
		p.add(new JLabel(rb.getString("dialog.numberOfProblems")));
		p.add(sp);
		if(JOptionPane.OK_OPTION!=JOptionPane.showInternalConfirmDialog(this, p, rb.getString("dialog.selectNumberOfProblems"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) {
			return;
		}
		String[] el = new String[spModel.getNumber().intValue()];
		for(int i = 0; i < el.length; i++) {
			el[i] = formatQuestion(tf.getText(), i+1);
		}
		model.setProblemLabels(el);
		problemsTableModel.fireTableDataChanged();
	}
	
	public void groupsSet1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JPanel p = new JPanel(new GridLayout(1,2));
		int ac = model.getGroupLabels()==null ? 0 : model.getGroupLabels().length;
		if(ac<2) ac = 4;
		SpinnerNumberModel spModel = new SpinnerNumberModel(ac, 2, 125, 1);
		JSpinner sp = new JSpinner(spModel);
		p.add(new JLabel(rb.getString("dialog.numberOfGroups")));
		p.add(sp);
		if(JOptionPane.OK_OPTION!=JOptionPane.showInternalConfirmDialog(this, p, rb.getString("dialog.selectNumberOfGroups"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) {
			return;
		}
		model.setGroupLabels(generateGroupLabels(spModel.getNumber().intValue()));
		groupsTableModel.fireTableDataChanged();
	}
	
	private String[] generateGroupLabels(int n) {
		String[] el = new String[n];
		if(n<=5) {
			for(int i = 0; i<n; i++) {
				el[i] = Character.valueOf((char)('A'+i)).toString();
			}
			return el;
		} else if(n<=25) {
			int broj = 0;
	ou:		for(int i = 0; i < 5; i++) {
				for(int j = 0; j < 5; j++) {
					broj++;
					el[broj-1] = Character.valueOf((char)('A'+i)).toString()+Character.valueOf((char)('A'+j)).toString();
					if(broj>=n) break ou;
				}
			}
		} else {
			int broj = 0;
	ou:		for(int i = 0; i < 5; i++) {
				for(int j = 0; j < 5; j++) {
					for(int k = 0; k < 5; k++) {
						broj++;
						el[broj-1] = Character.valueOf((char)('A'+i)).toString()+Character.valueOf((char)('A'+j)).toString()+Character.valueOf((char)('A'+k)).toString();
						if(broj>=n) break ou;
					}
				}
			}
		}
		return el;
	}

	private String formatQuestion(String text, int n) {
		text = text.trim();
		text = text.replaceAll("#", String.valueOf(n));
		return text;
	}

	public void scheduleLoad1() {
		ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectMMFile"));
		if(JFileChooser.APPROVE_OPTION != fc.showOpenDialog(this)) {
			return;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
		
		Map<String,PMStudent> students = new LinkedHashMap<String, PMStudent>(1000);
		Map<String,PMRoom> rooms = new LinkedHashMap<String, PMRoom>(100);
		
		for(PMRoom r : model.getRooms()) {
			rooms.put(r.getName(), r);
		}
		for(PMStudent s : model.getStudents()) {
			students.put(s.getJmbag(), s);
		}

		model.getStudentSchedule().clear();
		model.incrementStudentScheduleVersion();
		
		File f = fc.getSelectedFile();
		BufferedReader br = null;
		boolean err = false;
		try {
			br = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(f)),"UTF-8"));
			br.readLine();
			br.readLine();
			while(true) {
				String line = br.readLine();
				if(line==null) break;
				if(line.isEmpty()) continue;
				String[] elems = line.split("#");
				if(elems.length!=5) {
					JOptionPane.showMessageDialog(gui, ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale()).getString("err.illegalFileRow")+line);
					err = true;
					break;
				}
				PMRoom r = rooms.get(elems[1]);
				if(r==null) {
					r = new PMRoom(elems[1], 1);
					rooms.put(r.getName(), r);
					model.getRooms().add(r);
				}
				String jmbag = elems[3].substring(1,elems[3].length()-2);
				PMStudent s = students.get(jmbag);
				if(s==null) {
					String lastName = elems[2];
					int p = lastName.indexOf(',');
					String firstName = lastName.substring(p+1).trim();
					lastName = lastName.substring(0, p).trim();
					p = firstName.indexOf('(');
					firstName = firstName.substring(0, p).trim();
					s = new PMStudent(jmbag, lastName, firstName);
					students.put(jmbag, s);
					model.getStudents().add(s);
				}
				List<PMStudent> list = model.getStudentSchedule().get(r);
				if(list==null) {
					list = new ArrayList<PMStudent>();
					model.getStudentSchedule().put(r, list);
				}
				list.add(s);
			}
			br.close();
			br = null;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(gui, ex.getMessage());
			err = true;
		} finally {
			if(br!=null) try { br.close(); } catch(Exception ex) {}
		}
		if(err) return;
		List<PMRoom> list = new ArrayList<PMRoom>(rooms.size());
		for(Map.Entry<String,PMRoom> e : rooms.entrySet()) {
			list.add(e.getValue());
		}
		Collections.sort(model.getRooms());
		Collections.sort(model.getStudents());
		for(PMRoom r : model.getRooms()) {
			r.setTaken(false);
		}
		for(PMRoom r : model.getStudentSchedule().keySet()) {
			r.setTaken(true);
			List<PMStudent> list2 = model.getStudentSchedule().get(r);
			if(list2!=null && list2.size()>0) {
				r.setCapacity(list2.size());
			}
		}
		roomsTableModel.fireTableDataChanged();
		studentsTableModel.fireTableDataChanged();
		studentScheduleTableModel.fireTableDataChanged();
	}

	private boolean isModelReady() {
		if(StringUtil.isStringBlank(model.getAcademicYear())) return false;
		if(StringUtil.isStringBlank(model.getSemester())) return false;
		if(StringUtil.isStringBlank(model.getCourseName())) return false;
		if(StringUtil.isStringBlank(model.getCourseISVU())) return false;
		if(StringUtil.isStringBlank(model.getAssessmentName())) return false;
		if(model.getDate()==null) return false;
		return true;
	}
	
	private Assessment buildAssessment() {

		if(!isModelReady()) return null;
		
		Map<String, User> userMap = new HashMap<String, User>(1000);
		
		// Akademska godine, kolegij, primjerak kolegija
		YearSemester ys = new YearSemester("1",model.getAcademicYear(),model.getSemester());
		Course c = new Course();
		c.setIsvuCode(model.getCourseISVU());
		c.setName(model.getCourseName());
		CourseInstance ci = new CourseInstance();
		ci.setCourse(c);
		ci.setYearSemester(ys);
		
		// Ispit
		Assessment a = new Assessment();
		a.setCourseInstance(ci);
		a.setName(model.getAssessmentName());
		ci.getAssessments().add(a);
		a.setShortName("xyz");
		
		// Pocetak ispita
		CourseWideEvent e = new CourseWideEvent();
		e.setCourseInstance(ci);
		e.setDuration(120);
		e.setStart(model.getDate());
		a.setEvent(e);
		
		// Konfiguracija
		AssessmentConfChoice acc = new AssessmentConfChoice();
		a.setAssessmentConfiguration(acc);
		acc.setAssessment(a);
		
		acc.setAnswersNumber(model.getAnswersCount());
		acc.setErrorColumn(!model.getErrorColumn().isEmpty());
		acc.setErrorColumnText(model.getErrorColumn());
		acc.setGroupsNum(model.getGroupLabels().length);
		acc.setGroupsLabels(tabbify(model.getGroupLabels()));
		acc.setPersonalizedGroups(false);
		acc.setProblemsNum(model.getProblemLabels().length);
		acc.setProblemsLabels(tabbify(model.getProblemLabels()));
		
		// Dvorane i raspored studenata
		for(PMRoom room : model.getStudentSchedule().keySet()) {
			AssessmentRoom r = new AssessmentRoom();
			r.setAssessment(a);
			r.setCapacity(room.getCapacity());
			r.setTaken(true);
			Room ro = new Room();
			ro.setId(room.getName());
			ro.setName(room.getName());
			ro.setShortName(room.getName());
			r.setRoom(ro);
			List<PMStudent> students = model.getStudentSchedule().get(room);
			Group g = new Group();
			g.setCompositeCourseID(c.getIsvuCode());
			g.setRelativePath(room.getName());
			r.setGroup(g);
			int position = 0;
			for(PMStudent s : students) {
				position++;
				UserGroup ug = new UserGroup();
				ug.setGroup(g);
				ug.setPosition(position);
				ug.setUser(getUser(userMap, s));
				g.getUsers().add(ug);
			}
			
			a.getRooms().add(r);
		}
		
		return a;
	}

	private String tabbify(String[] arr) {
		StringBuilder sb = new StringBuilder(arr.length*30);
		for(int i = 0; i < arr.length; i++) {
			if(i>0) {
				sb.append('\t');
			}
			sb.append(arr[i]);
		}
		return sb.toString();
	}

	private User getUser(Map<String, User> userMap, PMStudent s) {
		User u = userMap.get(s.getJmbag());
		if(u!=null) return u;
		u = new User();
		u.setJmbag(s.getJmbag());
		u.setLastName(s.getLastName());
		u.setFirstName(s.getFirstName());
		userMap.put(u.getJmbag(), u);
		return u;
	}

	public void save() {
		modelSave(false);
	}
	
	public void saveAs() {
		modelSave(true);
	}
	
	public void modelSave(boolean saveAsSemantics) {
		final ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		File saveFileName = null;
		if(saveAsSemantics || getProjectFileName()==null) {
			JFileChooser fc = new JFileChooser();
			String currentDir = prefs.get("currentDir", ".");
			fc.setCurrentDirectory(new File(currentDir));
			fc.setDialogTitle(rb.getString("dialog.selectProjectToSave"));
			fc.resetChoosableFileFilters();
			FileNameExtensionFilter filter = new FileNameExtensionFilter(rb.getString("filefilter.freeFormCreatorProject"), "fcp");
			fc.addChoosableFileFilter(filter);
			if(getProjectFileName()!=null) {
				fc.setSelectedFile(getProjectFileName());
			}
			if(JFileChooser.APPROVE_OPTION != fc.showSaveDialog(this)) {
				return;
			}
			prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
			saveFileName = fc.getSelectedFile();
			String name = saveFileName.getName();
			int ekstPos = name.indexOf('.');
			if(ekstPos==-1) {
				saveFileName = new File(saveFileName.getParentFile(), name+".fcp");
			} else {
				if(ekstPos+4!=name.length()) {  // ako nema troslovnu ekstenziju na kraju...
					saveFileName = new File(saveFileName.getParentFile(), name+".fcp");
				}
			}
		} else {
			saveFileName = getProjectFileName();
		}
		
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(saveFileName)), "UTF-8"));
			setProjectFileName(saveFileName);
			bw.write("v1.0\n");
			bw.write("cn="+denull(model.getCourseName())+"\n");
			bw.write("ci="+denull(model.getCourseISVU())+"\n");
			bw.write("ay="+denull(model.getAcademicYear())+"\n");
			bw.write("se="+denull(model.getSemester())+"\n");
			bw.write("an="+denull(model.getAssessmentName())+"\n");
			bw.write("dt="+(model.getDate()==null ? "" : new SimpleDateFormat("yyyy-MM-dd").format(model.getDate()))+"\n");
			bw.write("ac="+model.getAnswersCount()+"\n");
			bw.write("ec="+denull(model.getErrorColumn())+"\n");
			bw.write("pl="+(model.getProblemLabels()==null ? "0" : Integer.toString(model.getProblemLabels().length))+"\n");
			if(model.getProblemLabels()!=null) writeStringArray("pl", model.getProblemLabels(), bw);
			bw.write("gl="+(model.getGroupLabels()==null ? "0" : Integer.toString(model.getGroupLabels().length))+"\n");
			if(model.getGroupLabels()!=null) writeStringArray("gl", model.getGroupLabels(), bw);
			bw.write("st="+model.getStudents().size()+"\n");
			int c = 0;
			for(PMStudent s : model.getStudents()) {
				bw.write("st."+Integer.toString(c)+"="+s.getJmbag()+"\t"+s.getLastName()+"\t"+s.getFirstName()+"\n");
				c++;
			}
			bw.write("ro="+model.getRooms().size()+"\n");
			c = 0;
			for(PMRoom r : model.getRooms()) {
				bw.write("ro."+Integer.toString(c)+"="+r.getName()+"\t"+r.getCapacity()+"\t"+r.isTaken()+"\n");
				c++;
			}
			bw.write("sc="+model.getStudentSchedule().size()+"\n");
			c = 0;
			for(Map.Entry<PMRoom, List<PMStudent>> e : model.getStudentSchedule().entrySet()) {
				bw.write("sc."+Integer.toString(c)+"="+e.getKey().getName());
				if(e.getValue()!=null) {
					for(PMStudent s : e.getValue()) {
						bw.write("\t"+s.getJmbag());
					}
				}
				bw.write("\n");
				c++;
			}
			bw.close();
			bw = null;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(gui, ex.getMessage());
			return;
		} finally {
			if(bw!=null) try { bw.close(); } catch(Exception ignorable) {}
		}
		Iterator<ProjectGUI> it = gui.internalFramesList.iterator();
		String myAP = getProjectFileName().getAbsolutePath();
		while(it.hasNext()) {
			ProjectGUI g = it.next();
			if(g.getProjectFileName()==null) continue;
			if(myAP.equals(g.getProjectFileName().getAbsolutePath()) && g!=ProjectGUI.this) {
				g.dispose();
			}
		}
	}

	public boolean modelLoad() {
		final ResourceBundle rb = ResourceBundle.getBundle("ActionLabels", I18NUtil.getCurrentLocale());
		JFileChooser fc = new JFileChooser();
		String currentDir = prefs.get("currentDir", ".");
		fc.setCurrentDirectory(new File(currentDir));
		fc.setDialogTitle(rb.getString("dialog.selectProjectToOpen"));
		fc.resetChoosableFileFilters();
		FileNameExtensionFilter filter = new FileNameExtensionFilter(rb.getString("filefilter.freeFormCreatorProject"), "fcp");
		fc.addChoosableFileFilter(filter);
		if(JFileChooser.APPROVE_OPTION != fc.showOpenDialog(this)) {
			return false;
		}
		prefs.put("currentDir", fc.getCurrentDirectory().getAbsolutePath());
		
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(fc.getSelectedFile())), "UTF-8"));
			String line;
			
			String version = br.readLine();
			if(!version.equals("v1.0")) {
				throw new Exception("Unsupported version of project file.");
			}
			line = readLine(br, "cn=");
			model.setCourseName(line.substring(3));
			line = readLine(br, "ci=");
			model.setCourseISVU(line.substring(3));
			line = readLine(br, "ay=");
			model.setAcademicYear(line.substring(3));
			line = readLine(br, "se=");
			model.setSemester(line.substring(3));
			line = readLine(br, "an=");
			model.setAssessmentName(line.substring(3));
			line = readLine(br, "dt=").substring(3);
			if(line.isEmpty()) {
				model.setDate(null);
			} else {
				model.setDate(new SimpleDateFormat("yyyy-MM-dd").parse(line));
			}
			line = readLine(br, "ac=");
			model.setAnswersCount(Integer.parseInt(line.substring(3)));
			line = readLine(br, "ec=");
			model.setErrorColumn(line.substring(3));

			line = readLine(br, "pl=");
			int no = Integer.parseInt(line.substring(3));
			if(no<1) {
				model.setProblemLabels(null);
			} else {
				model.setProblemLabels(readStringArray("pl", no, br));
			}
			
			line = readLine(br, "gl=");
			no = Integer.parseInt(line.substring(3));
			if(no<1) {
				model.setGroupLabels(null);
			} else {
				model.setGroupLabels(readStringArray("gl", no, br));
			}

			line = readLine(br, "st=");
			no = Integer.parseInt(line.substring(3));
			if(no<1) {
				model.setStudents(new ArrayList<PMStudent>());
			} else {
				model.setStudents(readStudents(no, br));
			}
			
			line = readLine(br, "ro=");
			no = Integer.parseInt(line.substring(3));
			if(no<1) {
				model.setRooms(new ArrayList<PMRoom>());
			} else {
				model.setRooms(readRooms(no, br));
			}
			
			line = readLine(br, "sc=");
			no = Integer.parseInt(line.substring(3));
			if(no<1) {
				model.setStudentSchedule(new LinkedHashMap<PMRoom, List<PMStudent>>());
			} else {
				model.setStudentSchedule(readStudentSchedule(no, br));
			}

			br.close();
			br = null;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(gui, ex.getMessage());
			return false;
		} finally {
			if(br!=null) try { br.close(); } catch(Exception ignorable) {}
		}
		setProjectFileName(fc.getSelectedFile());
		generalDataTableModel.fireTableDataChanged();
		studentsTableModel.fireTableDataChanged();
		groupsTableModel.fireTableDataChanged();
		studentScheduleTableModel.fireTableDataChanged();
		return true;
	}

	private Map<PMRoom,List<PMStudent>> readStudentSchedule(int no, BufferedReader br) throws Exception {
		Map<PMRoom,List<PMStudent>> map = new LinkedHashMap<PMRoom, List<PMStudent>>(no*2);
		Map<String,PMStudent> studentMap = new HashMap<String, PMStudent>(model.getStudents().size()*2);
		for(PMStudent s : model.getStudents()) {
			studentMap.put(s.getJmbag(), s);
		}
		Map<String,PMRoom> roomMap = new HashMap<String, PMRoom>(model.getRooms().size()*2);
		for(PMRoom r : model.getRooms()) {
			roomMap.put(r.getName(), r);
		}
		for(int i = 0; i < no; i++) {
			String line = br.readLine();
			String prefix = "sc."+i+"=";
			if(!line.startsWith(prefix)) throw new Exception("Misformatted file.");
			String[] el = line.substring(prefix.length()).split("\t");
			PMRoom r = roomMap.get(deutf8bom(el[0]));
			if(r==null) {
				throw new Exception("Corrupted project file: found scheduled nonexistent room ("+el[0]+").");
			}
			List<PMStudent> list = new ArrayList<PMStudent>(el.length-1);
			for(int j = 1; j < el.length; j++) {
				PMStudent s = studentMap.get(deutf8bom(el[j]));
				if(s==null) {
					throw new Exception("Corrupted project file: found scheduled nonexistent student ("+el[j]+").");
				}
				list.add(s);
			}
			map.put(r, list);
		}
		return map;
	}

	private List<PMRoom> readRooms(int no, BufferedReader br) throws Exception {
		List<PMRoom> list = new ArrayList<PMRoom>();
		for(int i = 0; i < no; i++) {
			String line = br.readLine();
			String prefix = "ro."+i+"=";
			if(!line.startsWith(prefix)) throw new Exception("Misformatted file.");
			String[] el = line.substring(prefix.length()).split("\t");
			PMRoom r = new PMRoom(deutf8bom(el[0]), Integer.parseInt(el[1]));
			r.setTaken(Boolean.valueOf(el[2]).booleanValue());
			list.add(r);
		}
		return list;
	}

	private List<PMStudent> readStudents(int no, BufferedReader br) throws Exception {
		List<PMStudent> list = new ArrayList<PMStudent>();
		for(int i = 0; i < no; i++) {
			String line = br.readLine();
			String prefix = "st."+i+"=";
			if(!line.startsWith(prefix)) throw new Exception("Misformatted file.");
			String[] el = line.substring(prefix.length()).split("\t");
			list.add(new PMStudent(deutf8bom(el[0]), el[1], el[2]));
		}
		return list;
	}

	private String deutf8bom(String s) {
		if(s==null || s.isEmpty()) return s;
		if((int)s.charAt(0)==65279) return s.substring(1);
		return s;
	}

	private String[] readStringArray(String prefix, int no, BufferedReader br) throws Exception {
		String[] el = new String[no];
		for(int i = 0; i < el.length; i++) {
			String line = br.readLine();
			String st = prefix+"."+i+"=";
			if(!line.startsWith(st)) throw new Exception("Misformatted file.");
			el[i] = line.substring(st.length());
		}
		return el;
	}

	private String readLine(BufferedReader br, String prefix) throws Exception {
		String line = br.readLine();
		if(line.startsWith(prefix)) return line;
		throw new Exception("Misformatted file.");
	}

	private void writeStringArray(String prefix, String[] arr, BufferedWriter bw) throws IOException {
		if(arr==null || arr.length==0) return;
		int i = 0;
		for(String s : arr) {
			bw.write(prefix);
			bw.write(".");
			bw.write(Integer.toString(i));
			bw.write("=");
			bw.write(denull(s));
			bw.write("\n");
			i++;
		}
	}
}
