package se.unlogic.eagledns.zoneproviders.db;

import org.apache.log4j.Logger;
import org.xbill.DNS.Zone;
import se.unlogic.eagledns.SecondaryZone;
import se.unlogic.eagledns.ZoneProvider;
import se.unlogic.eagledns.zoneproviders.db.beans.DBRecord;
import se.unlogic.eagledns.zoneproviders.db.beans.DBSecondaryZone;
import se.unlogic.eagledns.zoneproviders.db.beans.DBZone;
import se.unlogic.standardutils.dao.AnnotatedDAO;
import se.unlogic.standardutils.dao.HighLevelQuery;
import se.unlogic.standardutils.dao.QueryParameterFactory;
import se.unlogic.standardutils.dao.SimpleAnnotatedDAOFactory;
import se.unlogic.standardutils.dao.SimpleDataSource;
import se.unlogic.standardutils.dao.TransactionHandler;
import se.unlogic.standardutils.reflection.ReflectionUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class DBZoneProvider implements ZoneProvider {

   private static final Field RECORD_RELATION = ReflectionUtils.getField(DBZone.class, "records");

   private Logger log = Logger.getLogger(this.getClass());

   private String name;
   private String driver;
   private String url;
   private String username;
   private String password;

   private SimpleAnnotatedDAOFactory annotatedDAOFactory;
   private AnnotatedDAO<DBZone> zoneDAO;
   private AnnotatedDAO<DBRecord> recordDAO;
   private HighLevelQuery<DBZone> primaryZoneQuery;
   private HighLevelQuery<DBZone> secondaryZoneQuery;
   private QueryParameterFactory<DBZone, Integer> zoneIDQueryParameterFactory;
   private QueryParameterFactory<DBRecord, DBZone> recordZoneQueryParameterFactory;

   public void init(String name) throws ClassNotFoundException {
      this.name = name;

      DataSource dataSource;

      try {
         dataSource = new SimpleDataSource(driver, url, username, password);

      } catch (ClassNotFoundException e) {

         log.error("Unable to load JDBC driver " + driver, e);

         throw e;
      }

      this.annotatedDAOFactory = new SimpleAnnotatedDAOFactory();

      this.zoneDAO = new AnnotatedDAO<DBZone>(dataSource,DBZone.class, annotatedDAOFactory);
      this.recordDAO = new AnnotatedDAO<DBRecord>(dataSource,DBRecord.class, annotatedDAOFactory);

      QueryParameterFactory<DBZone, Boolean> zoneTypeParamFactory = zoneDAO.getParamFactory("secondary", boolean.class);

      this.primaryZoneQuery = new HighLevelQuery<DBZone>(zoneTypeParamFactory.getParameter(false),RECORD_RELATION);
      this.secondaryZoneQuery = new HighLevelQuery<DBZone>(zoneTypeParamFactory.getParameter(true),RECORD_RELATION);

      this.zoneIDQueryParameterFactory = zoneDAO.getParamFactory("zoneID", Integer.class);
      this.recordZoneQueryParameterFactory = recordDAO.getParamFactory("zone", DBZone.class);
   }

   public Collection<Zone> getPrimaryZones() {

      try {
         List<DBZone> dbZones = this.zoneDAO.getAll(primaryZoneQuery);

         if(dbZones != null){

            ArrayList<Zone> zones = new ArrayList<Zone>(dbZones.size());

            for(DBZone dbZone : dbZones){

               try {
                  zones.add(dbZone.toZone());

               } catch (IOException e) {

                  log.error("Unable to parse zone " + dbZone.getName(),e);
               }
            }

            return zones;
         }

      } catch (SQLException e) {

         log.error("Error getting primary zones from DB zone provider " + name,e);
      }

      return null;
   }

   public Collection<SecondaryZone> getSecondaryZones() {

      try {
         List<DBZone> dbZones = this.zoneDAO.getAll(this.secondaryZoneQuery);

         if(dbZones != null){

            ArrayList<SecondaryZone> zones = new ArrayList<SecondaryZone>(dbZones.size());

            for(DBZone dbZone : dbZones){

               try {
                  DBSecondaryZone secondaryZone = new DBSecondaryZone(dbZone.getZoneID() ,dbZone.getName(), dbZone.getPrimaryDNS(), dbZone.getDclass());

                  if(dbZone.getRecords() != null){
                     secondaryZone.setZoneCopy(dbZone.toZone());
                     secondaryZone.setDownloaded(dbZone.getDownloaded());
                  }

                  zones.add(secondaryZone);

               } catch (IOException e) {

                  log.error("Unable to parse zone " + dbZone.getName(),e);
               }
            }

            return zones;
         }

      } catch (SQLException e) {

         log.error("Error getting secondary zones from DB zone provider " + name,e);
      }

      return null;
   }

   public void zoneUpdated(SecondaryZone zone) {

      if(!(zone instanceof DBSecondaryZone)){

         log.warn(zone.getClass() + " is not an instance of " + DBSecondaryZone.class + ", ignoring zone update");

         return;
      }

      Integer zoneID = ((DBSecondaryZone)zone).getZoneID();

      TransactionHandler transactionHandler = null;

      try {
         transactionHandler = zoneDAO.createTransaction();

         DBZone dbZone = this.zoneDAO.get(new HighLevelQuery<DBZone>(this.zoneIDQueryParameterFactory.getParameter(zoneID),(Field)null),transactionHandler);


         if(dbZone == null){

            log.warn("Unable to find secondary zone with zoneID " + zoneID + " in DB, ignoring zone update");

            return;
         }

         dbZone.parse(zone.getZoneCopy(), true);

         zoneDAO.update(dbZone,transactionHandler, null);

         recordDAO.delete(new HighLevelQuery<DBRecord>(recordZoneQueryParameterFactory.getParameter(dbZone),(Field)null), transactionHandler);

         if(dbZone.getRecords() != null){

            for(DBRecord dbRecord : dbZone.getRecords()){

               dbRecord.setZone(dbZone);

               this.recordDAO.add(dbRecord, transactionHandler, null);
            }
         }

         transactionHandler.commit();

         log.debug("Changes in seconday zone " + dbZone + " saved");

      } catch (SQLException e) {

         log.error("Unable to save changes in secondary zone " + zone.getZoneName(), e);
         TransactionHandler.autoClose(transactionHandler);
      }
   }

   public void zoneChecked(SecondaryZone zone) {

      if(!(zone instanceof DBSecondaryZone)){

         log.warn(zone.getClass() + " is not an instance of " + DBSecondaryZone.class + ", ignoring zone check");

         return;
      }

      Integer zoneID = ((DBSecondaryZone)zone).getZoneID();

      TransactionHandler transactionHandler = null;

      try {
         transactionHandler = zoneDAO.createTransaction();

         DBZone dbZone = this.zoneDAO.get(new HighLevelQuery<DBZone>(this.zoneIDQueryParameterFactory.getParameter(zoneID), (Field)null),transactionHandler);

         if(dbZone == null){

            log.warn("Unable to find secondary zone with zoneID " + zoneID + " in DB, ignoring zone update");

            return;
         }

         dbZone.parse(zone.getZoneCopy(), true);

         zoneDAO.update(dbZone,transactionHandler, null);

         transactionHandler.commit();

         log.debug("Changes in seconday zone " + dbZone + " saved");

      } catch (SQLException e) {

         log.error("Unable to save changes in secondary zone " + zone.getZoneName(), e);
         TransactionHandler.autoClose(transactionHandler);
      }
   }

   public void unload() {

      //Nothing to do here...
   }

   public void setDriver(String driver) {

      this.driver = driver;
   }

   public void setUsername(String username) {

      this.username = username;
   }

   public void setPassword(String password) {

      this.password = password;
   }

   public void setUrl(String url) {

      this.url = url;
   }
}
