/**
 * DatabaseImpl.java - "Programmering i Java", 4.utgave - 2009-07-01
 *
 * Klassene på denne filen gir tilgang til en database via en databasepool.
 * Databasepoolen administreres av et objekt av klassen DbWrapperFabrikkImpl.
 * Dette objektet er registrert i rmi-registeret. En klient henvender seg til
 * det objektet for å få sitt eget, eksklusive, databasewrapper-objekt. Også
 * det er et fjernobjekt.Alle databasewrapper-objektene bruker den samme databasepoolen.
 *
 * Feilhåndtering: Alle metodene kan kaste RemoteException, som må behandles
 * på klientsiden. Metodene sjekker ikke returverdiene fra databasepool-metodene
 * for reservering og frigiving av databaseforbindelser.
 */

package mittBibliotek.database;
import mittBibliotek.Person;
import mittBibliotek.Opprydder;
import java.util.ArrayList;
import java.sql.*;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/*
 * Et objekt av klassen gir en forbindelse til en persondatabase.
 */
public class DatabaseImpl extends UnicastRemoteObject implements Database {
  private DatabasePool dbPool;

  public DatabaseImpl(DatabasePool dbPool) throws RemoteException {
    this.dbPool = dbPool;
  }

  /**
   * Returnerer en ArrayList som inneholder alle dataene i tabellen person.
   * ArrayListen inneholder person-objekter. Listen er sortert etter etternavn.
   * Returnerer null dersom SQLException er kastet.
   */
  public ArrayList<Person> finnAlle() throws RemoteException {
    ArrayList<Person>  alle = new ArrayList<Person> ();
    /* initcap() er en sql-funksjon som setter første bokstav i hvert ord stor, resten smått */
    String sqlsetning
         = "select persnr, fornavn, etternavn from person order by etternavn, fornavn";
    System.out.println(sqlsetning);

    Forbindelse forbindelse = null;
    ResultSet res = null;
    Statement setning = null;
    try {
      forbindelse = dbPool.reserverForbindelse();
      setning = forbindelse.getForbindelse().createStatement();
      res = setning.executeQuery(sqlsetning);
      while (res.next()) {
        int nr = res.getInt("persnr");
        String fornavn = res.getString("fornavn");
        String etternavn = res.getString("etternavn");
        Person navnet = new Person(nr, fornavn, etternavn);
        alle.add(navnet);
      }
    } catch (Exception e) {
      Opprydder.skrivMelding(e, "finnAlle()");
      alle = null;
    } finally {  // setningene nedenfor utføres alltid
      Opprydder.lukkResSet(res);
      Opprydder.lukkSetning(setning);
      dbPool.frigiForbindelse(forbindelse.getNr());
    }
    return alle;
  }

  /**
   * Endrer navn. Det nye navnet samt personens nummer sendes inn som argument.
   * Returnerer false dersom ingen person med dette nummeret eksisterer,
   * eller SQLException er inntruffet. I siste tilfelle skrives også melding til konsollet.
   */
  public boolean endreNavn(Person personen) throws RemoteException {
    int nr = personen.getPersonNr();
    String nyttFornavn = personen.getFornavn();
    String nyttEtternavn = personen.getEtternavn();
    String sqlsetning = "update person set fornavn = '" + nyttFornavn
          + "', etternavn = '" + nyttEtternavn + "' where persnr = " + nr;
    System.out.println(sqlsetning);

    Forbindelse forbindelse = null;
    Statement setning = null;
    try {
      forbindelse = dbPool.reserverForbindelse();
      setning = (forbindelse.getForbindelse()).createStatement();
      return (setning.executeUpdate(sqlsetning) != 0);
    } catch (Exception e) {
      Opprydder.skrivMelding(e, "endreNavn()");
    } finally {
      Opprydder.lukkSetning(setning);
      dbPool.frigiForbindelse(forbindelse.getNr());
    }
    return false;
  }

  /**
   * Registrerer ny person.
   * Nummer tildeles av systemet, og et fullstendig personobjekt returneres til klienten.
   * Dersom tabellen er tom, settes personnr lik 1, ellers settes det lik
   * hittil største nummer + 1.(På grunn av at data kan slettes,
   * kan denne metoden føre til hull i nummersekvensen.)
   * Metoden returnerer null dersom feil oppstått.
   */
  public Person registrerNyPerson(String fornavn, String etternavn) throws RemoteException {
    int nyttNr = 1; // dersom data ikke er i databasen
    boolean ok = false;
    int antForsøk = 0;
    do {
      Forbindelse forbindelse = null;
      ResultSet res = null;
      Statement setning = null;
      try {
        String sqlsetning = "select max(persnr) as maks from person";
        forbindelse = dbPool.reserverForbindelse();
        setning = (forbindelse.getForbindelse()).createStatement();
        res = setning.executeQuery(sqlsetning);
         /*Hvis det ikke er data i tabellen, vil maks() returnere SQL NULL. getInt() vil omforme denne til 0,
         * og nyttNr blir dermed 1 dersom det ikke er data i databasen. Det er ok.  */
        res.next();
        nyttNr = res.getInt("maks") + 1;
        sqlsetning = "insert into person values("
                           + nyttNr + ", '"  + fornavn + "', '" + etternavn + "')";
        System.out.println(sqlsetning);
        setning.executeUpdate(sqlsetning);
        ok = true;
      } catch (SQLException e) {
        /*
         * Dersom feilen skyldes at person med samme nummer eksisterer fra før,
         *  må det bety at en annen klient har vært inne i databasen mellom
         *  våre to sql-setninger. Vi kjører derfor gjennom opptil tre ganger til.
         *  (Dette er noe som vil inntreffe svært sjelden.)
         */
        if (antForsøk < 4) {
          antForsøk++;
        } else {
          Opprydder.skrivMelding(e, "registrerNyPerson()");
          return null;   // RETUR, feil oppstått (finally-blokken blir utført)
        }
      } finally {
        Opprydder.lukkResSet(res);
        Opprydder.lukkSetning(setning);
        dbPool.frigiForbindelse(forbindelse.getNr());
      }
    } while (!ok);
    return new Person(nyttNr, fornavn, etternavn);
  }

  /**
   * Sletter person med gitt nummer.
   * Returnerer false dersom personen ikke finnes, eller SQLException inntruffet.
   */
  public boolean slettPerson(int nr) throws RemoteException {
    String sqlsetning = "delete from person where persnr = " + nr;
    System.out.println(sqlsetning);

    Forbindelse forbindelse = null;
    Statement setning = null;
    try {
      forbindelse = dbPool.reserverForbindelse();
      setning = (forbindelse.getForbindelse()).createStatement();
      return (setning.executeUpdate(sqlsetning) != 0);
    } catch (Exception e) {
      Opprydder.skrivMelding(e, "slettPerson()");
    } finally {
      Opprydder.lukkSetning(setning);
      dbPool.frigiForbindelse(forbindelse.getNr());
    }
    return false;
  }
}