package se.unlogic.eagledns;

import org.apache.log4j.Logger;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Record;
import org.xbill.DNS.Zone;
import org.xbill.DNS.ZoneTransferException;
import org.xbill.DNS.ZoneTransferIn;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;


public class CachedSecondaryZone {

   private Logger log = Logger.getLogger(this.getClass());
   protected ZoneProvider zoneProvider;
   private SecondaryZone secondaryZone;

   public CachedSecondaryZone(ZoneProvider zoneProvider, SecondaryZone secondaryZone) {

      this.zoneProvider = zoneProvider;
      this.secondaryZone = secondaryZone;
      //this.update();

      if(this.secondaryZone.getZoneCopy() != null){

         log.info("Using stored zone data for sedondary zone " + this.secondaryZone.getZoneName());
      }
   }


   public SecondaryZone getSecondaryZone() {

      return secondaryZone;
   }


   public void setSecondaryZone(SecondaryZone secondaryZone) {

      this.secondaryZone = secondaryZone;
   }

   /**
    * Updates this secondary zone from the primary zone
    * @param axfrTimeout
    */
   public void update(int axfrTimeout) {


      try {
         ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(this.secondaryZone.getZoneName(), this.secondaryZone.getRemoteServerAddress(), null);
         xfrin.setDClass(DClass.value(this.secondaryZone.getDclass()));
         xfrin.setTimeout(axfrTimeout);

         List<?> records = xfrin.run();

         if (!xfrin.isAXFR()) {

            log.warn("Unable to transfer zone " + this.secondaryZone.getZoneName() + " from server " + this.secondaryZone.getRemoteServerAddress() + ", response is not a valid AXFR!");

            return;
         }

         Zone axfrZone = new Zone(this.secondaryZone.getZoneName(),records.toArray(new Record[records.size()]));

         log.debug("Zone " + this.secondaryZone.getZoneName() + " successfully transfered from server " + this.secondaryZone.getRemoteServerAddress());

         if(!axfrZone.getSOA().getName().equals(this.secondaryZone.getZoneName())){

            log.warn("Invalid AXFR zone name in response when updating secondary zone " + this.secondaryZone.getZoneName() + ". Got zone name " + axfrZone.getSOA().getName() + " in respons.");
         }

         if(this.secondaryZone.getZoneCopy() == null || this.secondaryZone.getZoneCopy().getSOA().getSerial() != axfrZone.getSOA().getSerial()){

            this.secondaryZone.setZoneCopy(axfrZone);
            this.secondaryZone.setDownloaded(new Timestamp(System.currentTimeMillis()));
            this.zoneProvider.zoneUpdated(this.secondaryZone);

            log.info("Zone " + this.secondaryZone.getZoneName() + " successfully updated from server " + this.secondaryZone.getRemoteServerAddress());
         }else{

            log.info("Zone " + this.secondaryZone.getZoneName() + " is already up to date with serial " + axfrZone.getSOA().getSerial());
            this.zoneProvider.zoneChecked(secondaryZone);
         }

      } catch (IOException e) {

         log.warn("Unable to transfer zone " + this.secondaryZone.getZoneName() + " from server " + this.secondaryZone.getRemoteServerAddress() + ", " + e);

         checkExpired();

      } catch (ZoneTransferException e) {

         log.warn("Unable to transfer zone " + this.secondaryZone.getZoneName() + " from server " + this.secondaryZone.getRemoteServerAddress() + ", " + e);

         checkExpired();

      }catch (RuntimeException e) {

         log.warn("Unable to transfer zone " + this.secondaryZone.getZoneName() + " from server " + this.secondaryZone.getRemoteServerAddress() + ", " + e);

         checkExpired();

      }finally{

         this.secondaryZone.setDownloaded(new Timestamp(System.currentTimeMillis()));
      }
   }


   private void checkExpired() {

      if(this.secondaryZone.getZoneCopy() != null && (System.currentTimeMillis() - this.secondaryZone.getDownloaded().getTime()) > (this.secondaryZone.getZoneCopy().getSOA().getExpire() * 1000)){

         log.warn("AXFR copy of secondary zone " + secondaryZone.getZoneName() + " has expired, deleting zone data...");

         this.secondaryZone.setZoneCopy(null);
         this.secondaryZone.setDownloaded(null);
         this.zoneProvider.zoneUpdated(this.secondaryZone);
      }
   }
}
