001package edu.pdx.cs.joy.family;
002
003import java.sql.Connection;
004import java.sql.SQLException;
005import java.util.HashMap;
006import java.util.List;
007import java.util.Map;
008
009/**
010 * Data Access Object implementation for managing FamilyTree entities in the database.
011 * Coordinates persistence of Person and Marriage objects.
012 */
013public class FamilyTreeDAOImpl implements FamilyTreeDAO {
014
015  private final Connection connection;
016  private final PersonDAO personDAO;
017
018  /**
019   * Creates a new FamilyTreeDAOImpl with the specified database connection.
020   *
021   * @param connection the database connection to use
022   */
023  public FamilyTreeDAOImpl(Connection connection) {
024    this.connection = connection;
025    this.personDAO = new PersonDAOImpl(connection);
026  }
027
028  /**
029   * Drops all family tree related tables from the database if they exist.
030   * Note: Must drop marriages first due to foreign key constraints.
031   *
032   * @param connection the database connection to use
033   * @throws SQLException if a database error occurs
034   */
035  public static void dropTables(Connection connection) throws SQLException {
036    MarriageDAO.dropTable(connection);
037    PersonDAO.dropTable(connection);
038  }
039
040  /**
041   * Creates all family tree related tables in the database.
042   * Note: Must create persons first due to foreign key constraints.
043   *
044   * @param connection the database connection to use
045   * @throws SQLException if a database error occurs
046   */
047  public static void createTables(Connection connection) throws SQLException {
048    PersonDAO.createTable(connection);
049    MarriageDAO.createTable(connection);
050  }
051
052  /**
053   * Saves a complete family tree to the database.
054   * This includes all persons and marriages in the tree.
055   *
056   * @param familyTree the family tree to save
057   * @throws SQLException if a database error occurs
058   */
059  @Override
060  public void save(FamilyTree familyTree) throws SQLException {
061    // First, save all persons
062    for (Person person : familyTree.getPeople()) {
063      personDAO.save(person);
064    }
065
066    // Build a person cache for marriage persistence
067    Map<Integer, Person> personCache = new HashMap<>();
068    for (Person person : familyTree.getPeople()) {
069      personCache.put(person.getId(), person);
070    }
071
072    // Then, save all marriages
073    MarriageDAO marriageDAO = new MarriageDAOImpl(connection, personCache);
074    for (Person person : familyTree.getPeople()) {
075      for (Marriage marriage : person.getMarriages()) {
076        // Only save each marriage once (from husband's perspective to avoid duplicates)
077        if (person.equals(marriage.getHusband())) {
078          marriageDAO.save(marriage);
079        }
080      }
081    }
082  }
083
084  /**
085   * Loads a complete family tree from the database.
086   * This includes all persons and marriages, with relationships properly resolved.
087   *
088   * @return the family tree loaded from the database
089   * @throws SQLException if a database error occurs
090   */
091  @Override
092  public FamilyTree load() throws SQLException {
093    FamilyTree familyTree = new FamilyTree();
094
095    // First pass: Load all persons and build cache
096    Map<Integer, Person> personCache = new HashMap<>();
097    Map<Integer, ParentIds> parentIdsMap = new HashMap<>();
098
099    String sql = "SELECT id, gender, first_name, middle_name, last_name, " +
100                 "father_id, mother_id, date_of_birth, date_of_death FROM persons ORDER BY id";
101
102    try (java.sql.Statement statement = connection.createStatement();
103         java.sql.ResultSet resultSet = statement.executeQuery(sql)) {
104
105      while (resultSet.next()) {
106        int id = resultSet.getInt("id");
107        String genderStr = resultSet.getString("gender");
108        Person.Gender gender = Person.Gender.valueOf(genderStr);
109
110        Person person = new Person(id, gender);
111        person.setFirstName(resultSet.getString("first_name"));
112        person.setMiddleName(resultSet.getString("middle_name"));
113        person.setLastName(resultSet.getString("last_name"));
114
115        // Store parent IDs for later resolution
116        int fatherId = resultSet.getInt("father_id");
117        boolean fatherIsNull = resultSet.wasNull();
118        int motherId = resultSet.getInt("mother_id");
119        boolean motherIsNull = resultSet.wasNull();
120
121        if (!fatherIsNull || !motherIsNull) {
122          parentIdsMap.put(id, new ParentIds(
123            fatherIsNull ? Person.UNKNOWN : fatherId,
124            motherIsNull ? Person.UNKNOWN : motherId
125          ));
126        }
127
128        java.sql.Timestamp dob = resultSet.getTimestamp("date_of_birth");
129        if (dob != null) {
130          person.setDateOfBirth(new java.util.Date(dob.getTime()));
131        }
132
133        java.sql.Timestamp dod = resultSet.getTimestamp("date_of_death");
134        if (dod != null) {
135          person.setDateOfDeath(new java.util.Date(dod.getTime()));
136        }
137
138        familyTree.addPerson(person);
139        personCache.put(id, person);
140      }
141    }
142
143    // Second pass: Resolve parent relationships
144    for (Map.Entry<Integer, ParentIds> entry : parentIdsMap.entrySet()) {
145      Person person = personCache.get(entry.getKey());
146      ParentIds parentIds = entry.getValue();
147
148      if (parentIds.fatherId != Person.UNKNOWN) {
149        Person father = personCache.get(parentIds.fatherId);
150        if (father != null) {
151          person.setFather(father);
152        }
153      }
154
155      if (parentIds.motherId != Person.UNKNOWN) {
156        Person mother = personCache.get(parentIds.motherId);
157        if (mother != null) {
158          person.setMother(mother);
159        }
160      }
161    }
162
163    // Third pass: Load all marriages
164    MarriageDAO marriageDAO = new MarriageDAOImpl(connection, personCache);
165    List<Marriage> marriages = marriageDAO.findAll();
166
167    for (Marriage marriage : marriages) {
168      // Add marriage to both spouses
169      marriage.getHusband().addMarriage(marriage);
170      marriage.getWife().addMarriage(marriage);
171    }
172
173    return familyTree;
174  }
175
176  /**
177   * Helper class to store parent IDs during loading.
178   */
179  private static class ParentIds {
180    final int fatherId;
181    final int motherId;
182
183    ParentIds(int fatherId, int motherId) {
184      this.fatherId = fatherId;
185      this.motherId = motherId;
186    }
187  }
188}
189