// Path: datomic/src/main/java/ske/folkeregister/datomic/util/Changes.java
// @SuppressWarnings("unchecked")
// public class Changes {
// 
//    private static final String TX_ATTR_FOR_ENTITY =
//       "[:find ?tx ?a :in $ ?e :where [?e ?a _ ?tx]]";
// 
//    /**
//     * Fra August Lilleaas: http://dbs-are-fn.com/2013/datomic_history_of_an_entity/
//     */
//    public static List<Map> changeOverTimeForEntity(Database db, Object entityId) {
//       return Peer
//          // Extracts all transaction/attributes for given database/entity
//          .q(TX_ATTR_FOR_ENTITY, db.history(), entityId).stream()
//          // Group all attribute changes for one transaction
//          .collect(Collectors.groupingBy(list -> list.get(0))).values().stream()
//          // Convert to map of timestamped changes
//          .map(changes -> {
//             final Object tx = changes.get(0).get(0);
//             return map(
//                "changes", mapChangesForEntity(db, entityId, changes),
//                "timestamp", db.asOf(tx).entity(tx).get(":db/txInstant")
//             );
//          })
//          // Return as a list of change-maps
//          .collect(Collectors.toList());
//    }
// 
//    private static List<Map> mapChangesForEntity(
//       Database db,
//       Object entityId,
//       List<List<Object>> changes
//    ) {
//       return changes.stream()
//          // Convert to map of attribute-changes
//          .map(list -> {
//             final Long tx = (Long) list.get(0);
//             final Object attr = list.get(1);
// 
//             // Get database before and after transaction occured
//             final Database dbBefore = db.asOf(tx - 1);
//             final Database dbAfter = db.asOf(tx);
// 
//             // Get the attribute name before and after transaction occured
//             final Object attrBefore = dbBefore.entity(attr).get(":db/ident");
//             final Object attrAfter = dbAfter.entity(attr).get(":db/ident");
// 
//             // Find both the old and the new value of the attribute
//             final Object oldVal = IO.attrToValue(dbBefore.entity(entityId), attrBefore);
//             final Object newVal = IO.attrToValue(dbAfter.entity(entityId), attrAfter);
// 
//             // Create map with attribute-name -> old/new value
//             return map(
//                "attr", attrAfter.toString(),
//                "old", oldVal,
//                "new", newVal
//             );
//          })
//          .collect(Collectors.toList());
//    }
// }
// 
// Path: datomic/src/main/java/ske/folkeregister/datomic/util/IO.java
// @SuppressWarnings("unchecked")
// public class IO {
// 
//    public static void transactAllFromFile(Connection conn, String filename) throws InterruptedException, ExecutionException {
//       for (List tx : readData(filename)) {
//          conn.transact(tx).get();
//       }
//    }
// 
//    public static Connection newMemConnection() {
//       String uri = "datomic:mem://" + UUID.randomUUID();
//       Peer.createDatabase(uri);
//       return Peer.connect(uri);
//    }
// 
//    public static Map<String, Object> entityToMap(Entity entity) {
//       return entity
//          .keySet()
//          .stream()
//          .collect(Collectors.toMap(
//             key -> key.substring(key.lastIndexOf("/") + 1),
//             key -> attrToValue(entity, key)
//          ));
//    }
// 
//    public static Object attrToValue(Entity entity, Object key) {
//       Object value = entity.get(key);
//       if (value instanceof Entity) {
//          value = entityToMap((Entity) value);
//       } else if (value instanceof Keyword) {
//          value = ((Keyword) value).getName();
//       }
//       return value;
//    }
// 
//    private static List<List> readData(String filename) {
//       return Util.readAll(new InputStreamReader(
//          Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)));
//    }
// }


import datomic.Connection;
import datomic.Database;
import datomic.Entity;
import datomic.Peer;
import ske.folkeregister.datomic.util.Changes;
import ske.folkeregister.datomic.util.IO;
import java.util.*;
import java.util.stream.Collectors;
import static datomic.Peer.tempid;
import static datomic.Util.*;
import static datomic.Util.list;
import static datomic.Util.map;

   @Override
   public void changeNameAndStatus(String ssn, String newName, String newStatus) throws Exception {
      updatePerson(map(
         ":person/ssn", ssn,
         ":person/sivilstatus", read(newStatus),
         ":person/name", newName
      ));
   }

   @Override
   public void moveToNewAddress(String ssn, String street, String number, String postnumber) throws Exception {
      Object newTmpAddressId = tempid("db.part/user");
      conn.transact(list(
         map(
            ":db/id", newTmpAddressId,
            ":address/street", street,
            ":address/streetnumber", number,
            ":address/postnumber", postnumber
         ),
         map(
            ":db/id", list(":person/ssn", ssn),
            ":person/address", newTmpAddressId
         )
      )).get();
   }

   @Override
   public List<Map> changesForPerson(String ssn) throws Exception {
      return findPersonBySSN(ssn)
// Path: datomic/src/main/java/ske/folkeregister/datomic/util/Changes.java
// @SuppressWarnings("unchecked")
// public class Changes {
// 
//    private static final String TX_ATTR_FOR_ENTITY =
//       "[:find ?tx ?a :in $ ?e :where [?e ?a _ ?tx]]";
// 
//    /**
//     * Fra August Lilleaas: http://dbs-are-fn.com/2013/datomic_history_of_an_entity/
//     */
//    public static List<Map> changeOverTimeForEntity(Database db, Object entityId) {
//       return Peer
//          // Extracts all transaction/attributes for given database/entity
//          .q(TX_ATTR_FOR_ENTITY, db.history(), entityId).stream()
//          // Group all attribute changes for one transaction
//          .collect(Collectors.groupingBy(list -> list.get(0))).values().stream()
//          // Convert to map of timestamped changes
//          .map(changes -> {
//             final Object tx = changes.get(0).get(0);
//             return map(
//                "changes", mapChangesForEntity(db, entityId, changes),
//                "timestamp", db.asOf(tx).entity(tx).get(":db/txInstant")
//             );
//          })
//          // Return as a list of change-maps
//          .collect(Collectors.toList());
//    }
// 
//    private static List<Map> mapChangesForEntity(
//       Database db,
//       Object entityId,
//       List<List<Object>> changes
//    ) {
//       return changes.stream()
//          // Convert to map of attribute-changes
//          .map(list -> {
//             final Long tx = (Long) list.get(0);
//             final Object attr = list.get(1);
// 
//             // Get database before and after transaction occured
//             final Database dbBefore = db.asOf(tx - 1);
//             final Database dbAfter = db.asOf(tx);
// 
//             // Get the attribute name before and after transaction occured
//             final Object attrBefore = dbBefore.entity(attr).get(":db/ident");
//             final Object attrAfter = dbAfter.entity(attr).get(":db/ident");
// 
//             // Find both the old and the new value of the attribute
//             final Object oldVal = IO.attrToValue(dbBefore.entity(entityId), attrBefore);
//             final Object newVal = IO.attrToValue(dbAfter.entity(entityId), attrAfter);
// 
//             // Create map with attribute-name -> old/new value
//             return map(
//                "attr", attrAfter.toString(),
//                "old", oldVal,
//                "new", newVal
//             );
//          })
//          .collect(Collectors.toList());
//    }
// }
// 
// Path: datomic/src/main/java/ske/folkeregister/datomic/util/IO.java
// @SuppressWarnings("unchecked")
// public class IO {
// 
//    public static void transactAllFromFile(Connection conn, String filename) throws InterruptedException, ExecutionException {
//       for (List tx : readData(filename)) {
//          conn.transact(tx).get();
//       }
//    }
// 
//    public static Connection newMemConnection() {
//       String uri = "datomic:mem://" + UUID.randomUUID();
//       Peer.createDatabase(uri);
//       return Peer.connect(uri);
//    }
// 
//    public static Map<String, Object> entityToMap(Entity entity) {
//       return entity
//          .keySet()
//          .stream()
//          .collect(Collectors.toMap(
//             key -> key.substring(key.lastIndexOf("/") + 1),
//             key -> attrToValue(entity, key)
//          ));
//    }
// 
//    public static Object attrToValue(Entity entity, Object key) {
//       Object value = entity.get(key);
//       if (value instanceof Entity) {
//          value = entityToMap((Entity) value);
//       } else if (value instanceof Keyword) {
//          value = ((Keyword) value).getName();
//       }
//       return value;
//    }
// 
//    private static List<List> readData(String filename) {
//       return Util.readAll(new InputStreamReader(
//          Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)));
//    }
// }


// Path: datomic/src/main/java/ske/folkeregister/datomic/person/DatomicPersonApi.java
import datomic.Connection;
import datomic.Database;
import datomic.Entity;
import datomic.Peer;
import ske.folkeregister.datomic.util.Changes;
import ske.folkeregister.datomic.util.IO;
import java.util.*;
import java.util.stream.Collectors;
import static datomic.Peer.tempid;
import static datomic.Util.*;
import static datomic.Util.list;
import static datomic.Util.map;


   @Override
   public void changeNameAndStatus(String ssn, String newName, String newStatus) throws Exception {
      updatePerson(map(
         ":person/ssn", ssn,
         ":person/sivilstatus", read(newStatus),
         ":person/name", newName
      ));
   }

   @Override
   public void moveToNewAddress(String ssn, String street, String number, String postnumber) throws Exception {
      Object newTmpAddressId = tempid("db.part/user");
      conn.transact(list(
         map(
            ":db/id", newTmpAddressId,
            ":address/street", street,
            ":address/streetnumber", number,
            ":address/postnumber", postnumber
         ),
         map(
            ":db/id", list(":person/ssn", ssn),
            ":person/address", newTmpAddressId
         )
      )).get();
   }

   @Override
   public List<Map> changesForPerson(String ssn) throws Exception {
      return findPersonBySSN(ssn)
         .map(id -> Changes.changeOverTimeForEntity(conn.db(), id))