package edu.byu.cs.roots.opg.chart.preset.templates;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import edu.byu.cs.roots.opg.chart.ChartMaker;
import edu.byu.cs.roots.opg.chart.ShapeInfo;
import edu.byu.cs.roots.opg.model.Event;
import edu.byu.cs.roots.opg.model.Individual;
import edu.byu.cs.roots.opg.model.OpgOptions;
import edu.byu.cs.roots.opg.model.OpgSession;
public class AncesBoxParent extends Box{
private static final long serialVersionUID = 1L;
//tree structure variables
public AncesBoxParent father;
public AncesBoxParent mother;
protected Event marriage = null;
public int maxGensOfTree; //this the maximum number of generations in this individual's tree (1 = self, 2 = self & parent(s), etc.)
public int maxGensInTree; //this the maximum number of generations in this individual's tree (1 = self, 2 = self & parent(s), etc.)
public int maxVisibleGensInTree; //duplicate of variable above, only it only follows the boxes that can be seen by not being collapsed
public int gen; // the generation this box is a part of (zero based)
protected int numInGen; //the vertical ordinal number (zero based) of this box in a generation
public double intersectUpperHeight = 0.0;
public double intersectLowerHeight = 0.0;
//size/position variables
/**
* Distance from origin to the top most ancestor of this box
* (The highest point of the entire subtree, including this box)
*/
public double upperSubTreeHeight;
/**
* Distance from origin to the bottom most ancestor of this box
* (The lowest point of the entire subtree, including this box)
*/
public double lowerSubTreeHeight;
protected double hPos;
public double vPos;
/**
* The fathers vertical offset from the centre of this indi
*/
public double fatherVOffset;
/**
* the mothers vertical offset from the centre of this indi
*/
public double motherVOffset;
protected static double scaler;
public double minHeight; //the minimum height for the current configuration of the tree
/**the upper bound of the individual box relative to the base position. Used to draw the box.
*/
public double upperBoxBound;
/**the lower bound of the individual box relative to the base position. Used to draw the box.
*/
public double lowerBoxBound;
protected double boxWidth;
private Rectangle2D.Double boxInfo; //this is where the coords and size are stored
public AncesBoxParent(Individual indi)
{
this.setIndi(indi);
setUpperBounds(new ArrayList<Double>(1));
setLowerBounds(new ArrayList<Double>(1));
setBoxInfo(new Rectangle2D.Double());
}
/**
* Recursively builds a tree of Boxes representing an individual's genealogy without
* duplicate subtrees. Note, the tree has no coordinates yet.
* @param curGen
* @param genPositions
*
*/
public void buildBoxTree(int curGen, OpgSession session)
{
maxGensInTree = 1;
getIndi().numberOfAncestors = 0;
OpgOptions opgOptions = session.getOpgOptions();
HashMap<String,String> duplicateMap = opgOptions.getDuplicateMap();
session.addIndiToTree(getIndi());
if (getIndi().father != null)
{
//add father
father = addSpecificBox(getIndi().father);
//set generational position
father.gen = curGen;
//if father hasn't appeared yet, trace the rest of the line. If he has, stop here
if (!getIndi().father.isInTree)
{
getIndi().father.isInTree = true;
father.buildBoxTree(curGen+1, session);
maxGensInTree = (maxGensInTree < father.maxGensInTree + 1)? father.maxGensInTree + 1: maxGensInTree;
getIndi().numberOfAncestors += getIndi().father.numberOfAncestors + 1;
}
else
{
maxGensInTree +=1;
if (!duplicateMap.containsKey(getIndi().father.id))
duplicateMap.put(getIndi().father.id, "");
}
//if(opgOptions.isCollapsed(getIndi().father))
// maxVisibleGensInTree = curGen - 2;
//else
maxVisibleGensInTree = (maxVisibleGensInTree < father.maxVisibleGensInTree + 1)? father.maxVisibleGensInTree + 1: maxVisibleGensInTree;
}
if (getIndi().mother != null)
{
//add mother
mother = addSpecificBox(getIndi().mother);
//set generational position
mother.gen = curGen;
//if mother hasn't appeared yet, trace the rest of the line. If she has, stop here
if (!getIndi().mother.isInTree)
{
getIndi().mother.isInTree = true;
mother.buildBoxTree(curGen+1, session);
maxGensInTree = (maxGensInTree < mother.maxGensInTree + 1)? mother.maxGensInTree + 1: maxGensInTree;
getIndi().numberOfAncestors += getIndi().mother.numberOfAncestors + 1;
}
else
{
maxGensInTree +=1;
if (!duplicateMap.containsKey(getIndi().mother.id))
duplicateMap.put(getIndi().mother.id, "");
}
//if(opgOptions.isCollapsed(getIndi().father))
// maxVisibleGensInTree = curGen - 2;
//else
maxVisibleGensInTree = (maxVisibleGensInTree < mother.maxVisibleGensInTree + 1)? mother.maxVisibleGensInTree + 1: maxVisibleGensInTree;
}
if(duplicateMap.containsKey(getIndi().id))
{
if (curGen != 1)
maxVisibleGensInTree = 0;
else
{
if(mother != null && father != null)
maxVisibleGensInTree = Math.max(father.maxVisibleGensInTree, mother.maxVisibleGensInTree)+1;
else if(mother != null)
maxVisibleGensInTree = mother.maxVisibleGensInTree+1;
else if(father != null)
maxVisibleGensInTree = father.maxVisibleGensInTree+1;
else
maxVisibleGensInTree = 0;
}
}
}
//Methods that MUST be inherited and overriden by the subclass AncesBoxes
public void calcCoords(PresetChartOptions options, ChartMaker maker, int curGen, StylingBoxScheme stylingBoxes, OpgSession session){};
public void drawAncesTreeRec(int curGen, ChartMargins chart, PresetChartOptions options, double x, double y, OpgSession session){};
public AncesBoxParent addSpecificBox(Individual indi){return null;};
//-----------------------------------------------------------------------
/**
* this method finds the closest distance two individuals (with their subtrees) can be while only touching at
* one point (assuming the bounds for all generations are at different distances)
*
* @param father - the individual on top
* @param mother - the individual on bottom
*/
public double calcIntersectDist(AncesBoxParent father, AncesBoxParent mother)
{
double maxDist = 0;
int maxGen = Math.min(father.getUpperBounds().size(), mother.getUpperBounds().size() );
//find the max distance between them (the closest they can be while "intersecting" only at one point)
for (int i = 0; i < maxGen; ++i)
{
double curDist = mother.getUpperBounds().get(i) - father.getLowerBounds().get(i);
if (curDist > maxDist)
maxDist = curDist;
}
return maxDist;
}
//-----------------------------------------------------------------------
public void setRelativePositions(int curGen, int maxGen)
{
//set father box position relative to this box
if (father != null && curGen < maxGen)
{
father.vPos = vPos + fatherVOffset;
father.setRelativePositions(curGen+1, maxGen);
}
//set mother box position relative to this box
if (mother != null)
{
mother.vPos = vPos + motherVOffset;
mother.setRelativePositions(curGen+1, maxGen);
}
}
//-----------------------------------------------------------------------
double setHeight(double newHeight)
{
//set the scaler so that chart will be the right height
double curHeight = upperSubTreeHeight - lowerSubTreeHeight;
if (curHeight < minHeight)
throw new IllegalArgumentException ("Cannot scale chart smaller than minimum");
scaler = newHeight/minHeight;
return scaler;
}
//-----------------------------------------------------------------------
public void setScaler(double newScaler)
{
if (newScaler < 0.99)
throw new IllegalArgumentException ("Cannot scale chart smaller than minimum");
else
scaler = newScaler;
}
//-----------------------------------------------------------------------
/**
* Recursively returns a LinkedList of all ancestors displayed on the chart.
* @param the list that will get built through recursion
* @param the starting generation, usually 0
* @param the max generation to search to, chosen by user
* @return a LinkedList of ShapeInfos, storing all visible ancestors
*/
public LinkedList<ShapeInfo> getBoxes(LinkedList<ShapeInfo> list, int curGen, int maxGen, OpgSession session)
{
OpgOptions opgOptions = session.getOpgOptions();
//If an ancestor is not supposed to be drawn, still sticks them on the list to check, since their triangle can be clicked, however
//Sends the current generation in as the max, so it wont recurse further.
if ((father != null) && (curGen < maxGen))
{
if(!opgOptions.isCollapsed(getIndi().father))
list = father.getBoxes(list, curGen + 1, maxGen, session);
else
list = father.getBoxes(list, maxGen, maxGen, session);
}
if ((mother != null) && (curGen < maxGen))
{
if(!opgOptions.isCollapsed(getIndi().mother))
list = mother.getBoxes(list, curGen + 1, maxGen, session);
else
list = mother.getBoxes(list, maxGen, maxGen, session);
}
if (getBoxInfo() == null)
System.out.println("ERROR: Null box info on Ancestor");
else
list.add(new ShapeInfo(getBoxInfo(), getIndi(), gen, true));
return list;
}
/**
* Recursively determines if the passed in point clicks any of the ancestors visible.
* @param x coord of click
* @param y coord of click
* @param current generation, usually 0
* @param max generation displayed, chosen by user
* @return the ShapeInfo of whoever was clicked, null if no intersect
*/
public ShapeInfo checkIntersect(double x, double y, int curGen, int maxGen, OpgSession session)
{
if(getBoxInfo() == null){
System.out.println("debug: WARNING - you tried to find the intersect on"
+ " an AncesBox for " + getIndi().namePrefix + ",whose boxInfo == " +
"null -> this would result nullPointerException and should be fixed!");
//Andrew - found this bug when looking at my family gedcom file for Elizabeth LaFlesh
// next time we meet I'll try to recreate it and we can look at it.
return null;
}
OpgOptions opgOptions = session.getOpgOptions();
ShapeInfo retVal = null;
if (getBoxInfo().contains(x, y))
return new ShapeInfo(getBoxInfo(), getIndi(), gen, true);
else
{
//If an ancestor is not supposed to be drawn, still sticks them on the list to check, since their triangle can be clicked, however
//Sends the current generation in as the max, so it wont recurse further.
if ((father != null) && (curGen < maxGen))
{
if(!opgOptions.isCollapsed(getIndi().father))
retVal = father.checkIntersect(x, y, curGen + 1, maxGen, session);
else
retVal = father.checkIntersect(x, y, maxGen, maxGen, session);
}
if ((retVal == null) && (mother != null) && (curGen < maxGen))
{
if(!opgOptions.isCollapsed(getIndi().mother))
retVal = mother.checkIntersect(x, y, curGen + 1, maxGen, session);
else
retVal = mother.checkIntersect(x, y, maxGen, maxGen, session);
}
return retVal;
}
}
protected double yXForm(double y)
{
return y * scaler;
}
public void setBoxStyle(StylingBoxScheme scheme){
if(father!=null)
father.setBoxStyle(scheme);
if(mother!=null)
mother.setBoxStyle(scheme);
if(gen >= scheme.AncesByGenList.size())
scheme.increaseAncesList(gen);
styleBox = scheme.getAncesStyle(gen);
styleBox.resetTemporaries();
}
public void setBoxInfo(Rectangle2D.Double boxInfo) {
this.boxInfo = boxInfo;
}
public Rectangle2D.Double getBoxInfo() {
return boxInfo;
}
}