package edu.byu.cs.roots.opg.model;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import javax.swing.JOptionPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.log4j.Logger;
import edu.byu.cs.roots.opg.chart.ChartDrawInfo;
import edu.byu.cs.roots.opg.chart.ChartMaker;
import edu.byu.cs.roots.opg.chart.ChartOptions;
import edu.byu.cs.roots.opg.chart.ChartType;
import edu.byu.cs.roots.opg.chart.multisheet.MultisheetChartMaker;
import edu.byu.cs.roots.opg.color.AncesteralColorSchemes;
import edu.byu.cs.roots.opg.color.ColorScheme;
import edu.byu.cs.roots.opg.color.DescendantColorSchemes;
import edu.byu.cs.roots.opg.conf.ConfigData;
import edu.byu.cs.roots.opg.exc.FailedToLoadException;
import edu.byu.cs.roots.opg.exc.FailedToSaveException;
import edu.byu.cs.roots.opg.io.GedcomParser;
import edu.byu.cs.roots.opg.io.GedcomRecord;
import edu.byu.cs.roots.opg.io.InvalidSyntaxException;
import edu.byu.cs.roots.opg.io.PAF5Parser;
import edu.byu.cs.roots.opg.io.Project;
import edu.byu.cs.roots.opg.nfs.AccessFamilySearch;
import edu.byu.cs.roots.opg.nfs.NFSDownloadThread;
import edu.byu.cs.roots.opg.nfs.UsernamePasswordException;
public class OpgSession {
public static Logger log = Logger.getLogger(OpgSession.class);
public SessionState state;
public String gedfile;
public String projfile;
private ArrayList<OpgPage> pages;
private OpgOptions opgOptions;
//private ArrayList<ChartOptions> options;
public ArrayList<Individual> names_dataProvider;
public OpgCursor cursor;
// public boolean showRuler;
public ConfigData config;
public boolean changed;
private String tempFile;
public ArrayList<ColorScheme> descSchemes;
public ArrayList<ColorScheme> ancesSchemes;
public GedcomRecord record;
private AccessFamilySearch access;
// private Parser parser;
//private ArrayList<ChartMaker> maker;
private int pageNumber;
/** Storage for all the individuals in the current box tree structure.
* Used to clear flags without stack overflow.*/
protected ArrayList<Individual> indisInTree;
/** Storage for all the Families in the current box tree structure.
* Used to clear flags without stack overflow.*/
protected ArrayList<Family> familiesInTree;
public NFSDownloadThread thread;
public String getTempFile()
{
return tempFile;
}
public void setTempFile(String tempFileName)
{
tempFile = tempFileName;
}
private ChangeListener listener = new ChangeListener(){
public void stateChanged(ChangeEvent e) {
setChanged(true);
}
};
/**
* A collection of raw chart drawing commands, this represents a chart file
* which has been loaded. No configuration can be done, this is for viewing
* purposes only
*/
public ChartDrawInfo hollowchart;
/**
* Chart used for displaying the preview of the chart to the user when adding
* additional charts to the order.
*/
ChartDrawInfo previewChart;
public OpgSession(){
state = SessionState.newsession;
gedfile = null;
projfile = null;
// parser = null;
record = null;
access = new AccessFamilySearch();
pageNumber = 0;
pages = new ArrayList<OpgPage>();
pages.add(new OpgPage());
opgOptions = new OpgOptions();
ChartOptions options = currentPage().getOptions(0);
options.addChangeListener(listener);
opgOptions.addChangeListener(listener);
names_dataProvider = new ArrayList<Individual>();
cursor = OpgCursor.MOVE;
descSchemes = new ArrayList<ColorScheme>();
ancesSchemes = new ArrayList<ColorScheme>();
for(AncesteralColorSchemes scheme:AncesteralColorSchemes.values()){
ancesSchemes.add(scheme.getScheme());
}
for(DescendantColorSchemes scheme:DescendantColorSchemes.values()){
descSchemes.add(scheme.getScheme());
}
options.setAncesScheme(ancesSchemes.get(0));
options.setDescScheme(descSchemes.get(0));
indisInTree = new ArrayList<Individual>();
familiesInTree = new ArrayList<Family>();
thread = new NFSDownloadThread(this);
}
public void clear(){
state = SessionState.newsession;
gedfile = null;
projfile = null;
pageNumber = 0;
// parser = null;
record = null;
opgOptions = new OpgOptions();
hollowchart = null;
pages = new ArrayList<OpgPage>();
pages.add(new OpgPage());
ChartOptions options = currentPage().getOptions(0);
// changeType(ChartType.values()[0]);
options.addChangeListener(listener);
opgOptions.addChangeListener(listener);
names_dataProvider = new ArrayList<Individual>();
descSchemes = new ArrayList<ColorScheme>();
ancesSchemes = new ArrayList<ColorScheme>();
for(AncesteralColorSchemes scheme:AncesteralColorSchemes.values()){
ancesSchemes.add(scheme.getScheme());
}
for(DescendantColorSchemes scheme:DescendantColorSchemes.values()){
descSchemes.add(scheme.getScheme());
}
options.setAncesScheme(ancesSchemes.get(0));
options.setDescScheme(descSchemes.get(0));
indisInTree = new ArrayList<Individual>();
familiesInTree = new ArrayList<Family>();
thread = new NFSDownloadThread(this);
}
public ChartDrawInfo getPreview()
{
return previewChart;
}
// public void download(String username, String password) throws UsernamePasswordException, IOException {
// clear();
// ChartOptions.firstLoad = true;
// changeType(ChartType.values()[0]);
//
// access.newUsernameAndPassword(username, password);
//
// int ok = access.getInformation(null);
// switch(ok) {
// case(1):
// throw new UsernamePasswordException();
// case(2):
// case(3):
// case(4):
// throw new IOException();
// }
// if (access.GetMore()) {
// Object[] opt = {"Yes", "No"};
// int n = JOptionPane.showOptionDialog(null,
// "Nine generations have been uploaded,\n" +
// "but there are still more to download.\n" +
// "Do you want to continue downloading your genealogy?",
// "One Page Genealogy",
// JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
// null, opt, opt[0]);
// if (n == 0)
// access.continueAfterNine(null);
// else if (n == 1)
// access.doNotContinue();
// }
// record = access.getGedcomRecord();
// record.linkChildrenToParents();
// record.linkSpouseToFamily();
// record.setNFS(true);
//
// hollowchart = null;
// state = SessionState.edit;
// names_dataProvider = getIndividualList();
// //does this have a gedfile?
// gedfile = "newdownload.ged";
// changed = true;
//
// this.getBaseOptions().setRoot(access.GetRoot(), this);
// changed = false;
// }
public void update(String username, String password) {
clear();
ChartOptions.firstLoad = true;
changeType(ChartType.values()[0]);
//check the root. if it is different than this person's person,
//then show the user who is the root, and get that information!
try {
access.resetUsernameAndPassword(username, password);
access.update(0, null);
}
catch (UsernamePasswordException e) {
JOptionPane.showMessageDialog(null,
e.getMessage(),
"Error!", JOptionPane.ERROR_MESSAGE);
log.debug("Error downloading file");
}
record = access.getGedcomRecord();
record.linkChildrenToParents();
record.linkSpouseToFamily();
hollowchart = null;
state = SessionState.edit;
names_dataProvider = getIndividualList();
//does this have a gedfile?
gedfile = "newdownload.ged";
setChanged(true);
pages.get(0).getFirstOptions().setRoot(access.GetRoot(), this);
resetChanged();
//check all the families!
}
public void loadGedcom(File file) throws IOException, InvalidSyntaxException{
String filename = file.getCanonicalPath();
GedcomParser gedparser = new GedcomParser(filename);
clear();
ChartOptions.firstLoad = true;
changeType(ChartType.values()[0]);
record = gedparser.parseGedcom();
hollowchart = null;
state = SessionState.edit;
names_dataProvider = getIndividualList();
gedfile = filename;
setChanged(true);
}
public void loadPAF5(File file) throws IOException{
String fileName = file.getCanonicalPath();
clear();
changeType(ChartType.values()[0]);
record = PAF5Parser.parsePAF5File(fileName);
hollowchart = null;
state = SessionState.edit;
names_dataProvider = getIndividualList();
gedfile = fileName;
setChanged(true);
}
public void open(File file) throws FailedToLoadException, IOException {
String filename = file.getCanonicalPath();
log.debug("attempting to open " + filename);
if(filename.endsWith(".ged") || filename.endsWith(".GED")){
try {
loadGedcom(file);
pages.get(0).getFirstOptions().setRoot(names_dataProvider.get(0), this);
resetChanged();
} catch (InvalidSyntaxException e) {
log.debug("Error loading gedcom",e);
throw new FailedToLoadException(e.toString());
}
} else if(filename.endsWith(".paf") || filename.endsWith(".PAF") ||
filename.endsWith(".zip") || filename.endsWith(".ZIP")){
try {
loadPAF5(file);
pages.get(0).getFirstOptions().setRoot(names_dataProvider.get(0), this);
resetChanged();
} catch (IOException e) {
log.debug("Error loading paf ",e);
throw new FailedToLoadException(e.toString());
}
}else if(filename.endsWith(".opg") || filename.endsWith(".OPG")){
Project p = null;
try{
p = Project.open(file);
}
catch(Exception e){
log.debug("Error loading" , e);
throw new FailedToLoadException(e.toString());
}
pages = p.pages;
gedfile = p.gedfile;
record = p.gedRecord;
opgOptions = p.opgOptions;
if (record != null && record.isNFS())
access.open(record, pages.get(0).getFirstOptions().getRoot());
descSchemes = new ArrayList<ColorScheme>();
ancesSchemes = new ArrayList<ColorScheme>();
for(AncesteralColorSchemes scheme:AncesteralColorSchemes.values()){
if(scheme.schemeclass == pages.get(0).getFirstOptions().getAncesScheme().getClass())
ancesSchemes.add(pages.get(0).getFirstOptions().getAncesScheme());
else ancesSchemes.add(scheme.getScheme());
}
for(DescendantColorSchemes scheme:DescendantColorSchemes.values()){
if(scheme.schemeclass == pages.get(0).getFirstOptions().getDescScheme().getClass())
descSchemes.add(pages.get(0).getFirstOptions().getDescScheme());
else descSchemes.add(scheme.getScheme());
}
log.debug("Loading chart with ancesScheme = " + pages.get(0).getFirstOptions().getAncesGens());
log.debug("Loading chart with descScheme = " + pages.get(0).getFirstOptions().getDescGens());
if(record == null) //if no record
{
//create editable chart from ged/paf
try{
if (gedfile.endsWith(".ged") || gedfile.endsWith(".GED"))
{
GedcomParser gedparser = new GedcomParser(gedfile);
record = gedparser.parseGedcom();
} else if (gedfile.endsWith(".paf") || gedfile.endsWith(".PAF") ||
gedfile.endsWith(".zip") || gedfile.endsWith(".ZIP"))
{
record = PAF5Parser.parsePAF5File(gedfile);
} else throw new Exception("Invalid genealogy file specified");
pages.get(0).getFirstOptions().setRoot(record.getIndividual(pages.get(0).getFirstOptions().getRoot().id), this);
hollowchart = null;
previewChart = p.charts==null?null:p.charts.size()==0?null:p.charts.get(0);
state = SessionState.edit;
names_dataProvider = getIndividualList();
changeType(opgOptions.getChartType());
setChanged(true);
}
catch(Exception e) //chart is only viewable
{
log.debug("Ack~!!!! could not open gedcom",e);
state = SessionState.view;
record = null;
gedfile = null;
hollowchart = p.charts==null?null:p.charts.size()==0?null:p.charts.get(0);;
previewChart = p.charts==null?null:p.charts.size()==0?null:p.charts.get(0);;
changeType(opgOptions.getChartType());
}
}
else //create chart using record
{
log.debug("Loaded record");
pages.get(0).getFirstOptions().setRoot(record.getIndividual(pages.get(0).getFirstOptions().getRoot().id), this);
hollowchart = null;
previewChart = p.charts==null?null:p.charts.size()==0?null:p.charts.get(0);;
state = SessionState.edit;
names_dataProvider = getIndividualList();
changeType(opgOptions.getChartType());
//changed = true;
}
projfile = filename;
//changed = false;
}
else{
throw new FailedToLoadException("Unsupported File Type");
}
}
/*
* Saves the current chart to disk as an .opg file
* fileName - the name of the file to which to save the chart (.opg will be appended if it is not
* already part of the file name
*
* throws: IllegalArgumentException if the fileName is null;
* IOException when a file i/o error occurs (eg. invalid file path, disk write error, etc.)
*/
public void save(File file, boolean useRec) throws FailedToSaveException, IOException {
if (hollowchart == null && gedfile == null)
throw new IllegalStateException("A chart must be created before saving.");
// ChartSerializer chartOut = new ChartSerializer();
// chartOut.createChart(getChart(), projfile);
// System.out.println("Saving chart with ancesScheme = " + options.getAncesScheme());
// System.out.println("Saving chart with descScheme = " + options.getDescScheme());
// System.out.println("Saving chart with root = " + options.getRoot().id);
Project export = new Project(getCharts(), pages, opgOptions, gedfile, record, useRec);
try{
Project.save(export, file);
projfile = file.getCanonicalPath();
}
catch(Exception e){
log.debug("Error Saving file", e);
throw new FailedToSaveException(e.toString());
}
resetChanged();
}
/**
* Sets the session root and whether or not to include said root's spouse's tree.
*
* (Root is not stored in the implementation of OpgSession but in the instance of
* opg.chart.ChartOptions that OpgSession contains.)
*
* @param indi is the individual to be set as root
* @param includeSpouse true if spouse's tree is also desired, else false
*/
public void setRoot(Individual indi, boolean includeSpouse){
//options.setRoot(parser.individualmap.get(indiID));\
currentPage().getFirstOptions().setRoot(indi, this);
currentPage().getFirstOptions().setIncludeSpouses(includeSpouse);
}
public Individual getCurrentPageBaseRoot(){
return currentPage().getFirstOptions().getRoot();
}
public Individual getBaseRoot(){
return pages.get(0).getFirstOptions().getRoot();
}
public ArrayList<Individual> getIndividualList() throws IllegalStateException
{
ArrayList<Individual> indiList = new ArrayList<Individual>();
Set<String> idset = record.getIndividuals().keySet();
Map<String, Individual> map = record.getIndividuals();
// log.debug("populating list");
for(String id:idset)
{
indiList.add(map.get(id));
}
// log.debug("sorting");
Collections.sort(indiList);
// log.debug("done");
return indiList;
}
public void changeType(ChartType type) {
log.debug("type change, creating maker");
System.out.println("Type Change " + type);
if(type!=null)
{
pageNumber = 0;
ChartOptions tempOptions = pages.get(0).getFirstOptions();
pages = new ArrayList<OpgPage>();
pages.add(new OpgPage(tempOptions, type, this));
currentPage().getFirstMaker().convertOpgOptions(opgOptions);
opgOptions.setChartType(type);
}
}
/**
* Gets the first maker of the first page. This is the maker where all 'general'
* information is stored and processed in.
* @return
*/
public ChartMaker getBaseMaker() {
return pages.get(0).getFirstMaker();
}
//TODO problems
public ChartMaker getMaker() {
// if (pageMakers == null)
// return null;
// return pageMakers.get(pageNumber).get(chartNumber);
if (currentPage().getFirstMaker() == null)
return null;
return currentPage().getFirstMaker();
}
public ChartMaker addMaker(ChartType type, Individual root, int genOffset) {
if (getMakerByRoot(root.id) != null)
{
System.out.println("Attempted to add duplicate maker");
if(type == ChartType.MULTISHEET && genOffset < ((MultisheetChartMaker)getMakerByRoot(root.id)).getGenOffset())
((MultisheetChartMaker)getMakerByRoot(root.id)).setGenOffset(genOffset);
return null;
}
ChartMaker retVal;
//Here is where it checks if it fits on current page, if does, inserts there
retVal = type.getMaker();
ChartOptions tempOptions = retVal.convertToSpecificOptions(new ChartOptions(getBaseOptions()));
tempOptions.setRoot(root, this);
double addedHeight = getChartHeight(retVal, tempOptions);//retVal.getChart(tempOptions, this).getYExtent();
if (pages.get(pages.size()-1).getRemainingHeight() > addedHeight){
retVal = pages.get(pages.size()-1).addMaker(type, root, genOffset, this);
root.pageId.setItem(pages.size() + ":" + pages.get(pages.size() - 1).getChartCount());
}
else{
//If not . . .
OpgPage tempPage = new OpgPage(pages.get(0).getFirstOptions(), type, root, this);
retVal = tempPage.getFirstMaker();
if (type == ChartType.MULTISHEET){
MultisheetChartMaker temp = (MultisheetChartMaker) retVal;
temp.setGenOffset(genOffset);
}
pages.add(tempPage);
root.pageId.setItem(pages.size()+"");
}
return retVal;
}
/**
* Used to determine the height of a chart, without recursively drawing new charts
* Only used for Multisheet Charts
* @return
*/
public double getChartHeight(ChartMaker maker, ChartOptions options){
double retVal = 0f;
((MultisheetChartMaker)maker).gettingChartHeight = true;
retVal = maker.getChart(options, this).getYExtent();
((MultisheetChartMaker)maker).gettingChartHeight = false;
return retVal;
}
public ChartMaker getMakerByRoot(String id){
ChartMaker retVal = null;
for (OpgPage page : pages){
retVal = page.getMakerByRoot(id);
if(retVal != null)
break;
}
return retVal;
}
public OpgPage getPageByRoot(String id){
for(OpgPage page : pages){
if(page.containsRoot(id))
return page;
}
return null;
}
public int getIndexOf(OpgPage page){
return pages.indexOf(page);
}
public ChartOptions getOptionsByRoot(String id){
ChartOptions retVal = null;
for (OpgPage page : pages){
retVal = page.getOptionsByRoot(id);
if(retVal != null)
break;
}
return retVal;
}
public ChartOptions getBaseOptions(){
return pages.get(0).getFirstOptions();
}
//TODO this has a problem
public ChartOptions getOptions(){
return currentPage().getFirstOptions();
}
public OpgOptions getOpgOptions(){
return opgOptions;
}
public void getIncMaker(){
if (pageNumber < pages.size() - 1)
pageNumber++;
}
public void getDecMaker(){
if (pageNumber > 0)
pageNumber--;
}
public int getPageNumber(){
return pageNumber;
}
public int getPages(){
return pages.size();
}
public OpgPage getPage(int i){
return pages.get(i);
}
//TODO this will probably also have to wipe each maker in the first page, except the first
public void resetPages(){
while (pages.size() > 1)
pages.remove(1);
}
public void resetIndiList(){
indisInTree = new ArrayList<Individual>();
}
public void resetIndiFlags(){
for (Individual indi : indisInTree)
indi.resetFlags();
resetIndiList();
}
/**
* Adds the current individual to the list of Indis in the current box tree structure.
* Allows resetting of flags without stack overflow.
* @param indi
*/
public void addIndiToTree(Individual indi){
if (!indisInTree.contains(indi))
indisInTree.add(indi);
}
public void resetFamilyList(){
familiesInTree = new ArrayList<Family>();
}
public void resetFamilyFlags(){
for (Family fam : familiesInTree)
fam.resetFlags();
}
/**
* Adds the family to the list of families in the current box tree structure.
* Allows resetting of flags without stack overflow
* @param fam
*/
public void addFamToTree(Family fam){
if (!familiesInTree.contains(fam))
familiesInTree.add(fam);
}
public OpgPage currentPage(){
return pages.get(pageNumber);
}
public boolean isChanged(){
return changed;
}
public void setChanged(boolean set){
changed = set;
}
public void resetChanged(){
changed = false;
}
public ArrayList<ChartDrawInfo> getCharts(){
ArrayList<ChartDrawInfo> retVal = null;
for (int i = 0; i < pages.size(); i++){
if (pages.get(i) == currentPage())
retVal = pages.get(i).processCharts(this);
else
pages.get(i).processCharts(this);
}
return retVal;
}
public ArrayList<ChartDrawInfo> getAllCharts(){
ArrayList<ChartDrawInfo> retVal = new ArrayList<ChartDrawInfo>();
for(int i = 0; i < pages.size(); i++){
retVal.addAll(pages.get(i).processCharts(this));
}
return retVal;
}
public edu.byu.cs.roots.opg.chart.ShapeInfo getIndiIntersect(double x, double y,
int maxAnces, int maxDesc, OpgSession session){
edu.byu.cs.roots.opg.chart.ShapeInfo retVal = null;
double chartHeight = 0;
for(int i = 0; i < currentPage().getChartCount(); i++){
retVal = currentPage().getMaker(i).getIndiIntersect(x, y-chartHeight, maxAnces, maxDesc, session);
chartHeight+=currentPage().getMaker(i).getChart(currentPage().getOptions(i), this).getYExtent();
if (retVal != null)
break;
}
return retVal;
}
}