import net.agmodel.metDriver.*;
import net.agmodel.physical.*;
import net.agmodel.weatherData.*;
import net.agmodel.metBroker.*;
import net.agmodel.utility.*;


import java.lang.reflect.*;
import java.util.*;
import java.text.*;

/**
 * Title:        Test harness for MetBroker drivers
 * Description:
 * Copyright:    Copyright (c) 2002
 * Company:      National Agricultural Research Organisation
 * @author Matthew Laurenson
 * @version 1.0
 */

public class DriverTestHarness implements MetSourceForDrivers{

  //your driver name class name, without the package net.agmodel.metDriver
  static final String driverName="FieldServer";

  //the identifier MetBroker will use for your data source
  static final String sourceID="fieldserver";

  //the test station's identifier
  static final String stationID="1";

  //username and password to use when retrieving metadata
  static final String metaUsername="yourDBusername";
  static final String metaPassword="yourDBpassword";

  //range of resolutions in the database
  static final Duration shortestResolution=Duration.ONE_HOUR;
  static final Duration longestResolution=Duration.ONE_DAY;

  // maximum time between database updates
  static final Duration timeliness=new Duration(3,DurationUnit.YEAR);

  // time to use for daily data summaries
  static final float dailyoffset=9.0F;

  // the username and password to use when retrieving data
  static final String username=metaUsername;
  static final String password=metaPassword;


  // the elements to retrieve during the test
  static final MetElement[] elementArray={MetElement.AIRTEMPERATURE, MetElement.RAIN, MetElement.WIND, MetElement.LEAFWETNESS, MetElement.RADIATION,MetElement.SOILTEMPERATURE};

  // the data resolution to request
  static final MetDuration resolution=MetDuration.HOURLY;

  // the timezone for the request and for formatting the output
  static final TimeZone tz=TimeZone.getTimeZone("JST");

  // set the test interval
  static Interval interval;
  static {
    Calendar c=Calendar.getInstance(tz);
    //set the starting time of the interval
    c.clear();
    c.set(Calendar.YEAR,2002);
    c.set(Calendar.MONTH,9); // remember java months start from 0
    c.set(Calendar.DAY_OF_MONTH,30);
    c.set(Calendar.HOUR_OF_DAY,8);
    interval=new Interval(c.getTime(),new Duration(7,DurationUnit.DAY));
    System.out.println("Interval "+interval);
  }

  //-----------------------------------------------------------------------------
  //Don't change anything from here down:
  //-----------------------------------------------------------------------------

  static final DateFormat df=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
  static {
    df.setTimeZone(tz);
  }


  public static void main(String[] args) {
    //Set the appropriate class name and constructor
    DriverTestHarness testHarness = new DriverTestHarness();
  }

  public DriverTestHarness() {

  //MetBroker runs with defaultTimeZone set to UTC
  //This is important because many JDBC drivers don't handle time zones well.
  //and need to flush these problems out early.
    try {
    	System.out.println("Before standard load");

      Class accessClass=Class.forName("net.agmodel.metDriver."+driverName);
      System.out.println("found driver class");
      Constructor accessConstructor=accessClass.getConstructor(new Class[] {MetSourceForDrivers.class});
      System.out.println("found constructor");
      MetAccessMechanism accessMechanism=(MetAccessMechanism) accessConstructor.newInstance(new Object[] {this});
      System.out.println("Constructed");

      Set elements=new HashSet(elementArray.length);
      for (int i=0;i<elementArray.length;i++)
        elements.add(elementArray[i]);

      StationMetRequest request=new StationMetRequest(interval,elements,resolution,sourceID,stationID,true,false);


      if (accessMechanism.connectForMetaData(metaUsername,metaPassword)) {
        System.out.println("Able to connect for metadata");
        accessMechanism.updateRegionList();
        accessMechanism.updateStationList(null);
        accessMechanism.disconnectFromMetaData();
      }
      else
        System.out.println("Couldn't connect for metatdata");

      if (accessMechanism.connectForData(username,password)){
    	  StationDataSetImpl result=getStationDataSet(request,stationID, "en");

        if (result!=null) {
          accessMechanism.queryForStation(request,stationID,result);
          System.out.println(result.dumpDuration(df,"\t","\n","Date/Time","no data"));
        }
        else
          System.out.println("Maybe the station didn't operate over thate period, or doesn't have any suitable data");

        accessMechanism.disconnectFromData();
      }
      else
        System.out.println("Couldn't connect for data");




    }
    catch (IllegalAccessException a) {
      a.printStackTrace();
    }

    catch (InvocationTargetException t) {
      t.printStackTrace();
    }

    catch (InstantiationException i) {
      i.printStackTrace();
    }

    catch (ClassNotFoundException c) {
      c.printStackTrace();
    }

    catch (NoSuchMethodException m) {
      m.printStackTrace();
    }

    catch (net.agmodel.utility.ConnectionException x) {
      x.printStackTrace();
    }

    catch (net.agmodel.utility.GeneralException g){
      g.printStackTrace();
    }


  }

  static {
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
  }
  static boolean[][] catalog=new boolean[MetElement.size()][MetDuration.size()];
  static Map regions=new HashMap();
  static Map stations=new HashMap();

  public String getID(){
    return sourceID;
  }

  public void addRegion(String regionID, MultilingualString regionName){
    System.out.println("Adding region "+regionID+" "+regionName);
    regions.put(regionID,regionName);
  }

  public void addStation(String hostDBIdentifier, MultilingualString placeName,
                        Location location,
                        Period operational, String regionID){
    System.out.println("Adding station ID "+hostDBIdentifier+" lat "+location.getLatitude()+" long "+location.getLongitude()+" alt "+location.getAltitude()+" operational "+operational+" region "+regionID);
    if (regionID!=null)
      if (!regions.containsKey(regionID))
        throw new IllegalArgumentException("Unknown region "+regionID+" adding station "+stationID);

    stations.put(hostDBIdentifier,new WeatherStation(sourceID,driverName,regionID,regionID,hostDBIdentifier,placeName.toString(),location,operational,shortestResolution));
  }

  public void recordElementStatus(final String stationID, final MetElement element,
                 final Duration resolution, final Period period, final boolean status){
    System.out.println("Adding catalog entry for station "+stationID+" element "+element+" resolution "+resolution+" period "+period+" status "+status);
    catalog[element.ord][MetDuration.categoriseDuration(resolution).ord]=status;
  }

  public void recordMultipleElementStatus(final String stationID, final MetElement[] elements,
                 final Duration[] resolutions, final Period period, final boolean status){
    for (int i=0;i<elements.length;i++)
      for (int j=0;j<resolutions.length;j++)
        recordElementStatus(stationID,elements[i],resolutions[j],period,status);
  }

  public boolean checkElementCatalog(final String stationID, final MetElement element,
                 final Duration resolution, final Period period){
    return catalog[element.ord][MetDuration.categoriseDuration(resolution).ord];
  }

  public WeatherStation getWeatherStation(String hostID, String language){
    return (WeatherStation) stations.get(hostID);
  }

  public String getSuggestedTimezone(){
    return tz.getID();
  }

  public float getSuggestedDailyOffset(){
    return this.dailyoffset;
  }

  public StationDataSetImpl getStationDataSet(MetRequest request, String stationID, String language){
    WeatherStation station=getWeatherStation(stationID,language);

    //First check that request is within the range of resolutions provided by the MetSource
    if ((getHighestResolution().compareTo(request.getResolution().getDuration(station.getShortestResolution()))>0 && !request.shouldInterpolate())
      ||(getLowestResolution().compareTo(request.getResolution().getDuration(station.getShortestResolution()))<0 && !request.shouldSummarise()))
      return null;

    //Then decide on the result resolution, based on the station's shortestResolution.
    Duration resultResolution=request.getResolution().getDuration(station.getShortestResolution());

    System.out.println("Before "+request.getDateExtremes());
    Interval queryInterval=adjustQueryInterval(request,station.getOperational(),resultResolution);
    if (queryInterval==null)
      return null;

    System.out.println("After "+queryInterval);
    return new StationDataSetImpl(getID(), station, queryInterval, resultResolution);
   // return new StationDataSetImpl(getID(),getWeatherStation(stationID,language),request.getDateExtremes(),resolution.getDuration(shortestResolution));
  }

  public void processSequence(MetElement element, MetSequence sequence, MetRequest request, Duration queryResolution, StationDataSetImpl result){
    System.out.println("Adding sequence for "+element);
    if (request.containsMetElement(element)) {
      if (sequence!=null)
        if (result.getResolution().compareTo(queryResolution)>0)
          result.addSequence(sequence.summarize(result.getResolution()));
        else
          result.addSequence(sequence);
    }

  }

  public String getSystemUsercode(){
    return metaUsername;
  }

  public String getSystemPassword(){
    return metaPassword;
  }

  Duration getLowestResolution(){
    return longestResolution;
  }

  Duration getHighestResolution(){
    return shortestResolution;
  }

  public Interval adjustQueryInterval(MetRequest request, Period stationOperational, Duration resultResolution) {
    java.util.Date start=request.getDateExtremes().getStart();
    java.util.Date end=request.getDateExtremes().getEnd();

    if (start.before(stationOperational.getStart()))
      start=resultResolution.subtractFromDate(stationOperational.getStart());
      //the subtractfromDate ensures that the first data item is accessible
      //despite the partially closed interval (]

    if (stationOperational.hasEnd() && end.after(stationOperational.getEnd()))
      end=stationOperational.getEnd();

    if (end.after(start)) {
      if (request.getResolution().compareTo(MetDuration.DAILY)==0) {
        TimeZone tz=TimeZone.getTimeZone(getSuggestedTimezone());
        Calendar morning=Calendar.getInstance(tz);
        morning.setTime(request.getDateExtremes().getStart());

        int offset=(int) Math.floor((double) getSuggestedDailyOffset());

        morning.set(Calendar.HOUR_OF_DAY,offset);
        morning.set(Calendar.MINUTE,0);
        morning.set(Calendar.SECOND,0);
        morning.set(Calendar.MILLISECOND,0);

        return new Interval( Duration.oneWithSameTimeAsTwo(start,morning.getTime()),
                                             Duration.oneWithSameTimeAsTwo(end,morning.getTime()));
      }
      else
        return new Interval(start,end);
    } //end.after(start)
    else
      return null;
  }

  public Date getRecent(){
    Calendar c=Calendar.getInstance();
    c.add(Calendar.DATE,-(int) timeliness.getValue(DurationUnit.DAY));
    return c.getTime();
  }

  public String getName(String str) {
      return driverName;
  }


}