package ucar.nc2.thredds;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.adde.AddeDatasetFactory;
import ucar.nc2.dods.DODSNetcdfFile;
import ucar.nc2.dataset.NetcdfDataset;

import thredds.catalog.*;

import java.util.List;
import java.io.IOException;
import java.awt.image.BufferedImage;

import dods.dap.DODSException;

/**
 * This opens a URL that refers to a dataset in a THREDDS catalog, and gets the thredds InvDataset object.
 * It examines the possible InvAccess objects, and choses one. optimized to open as an NetcdfDataset (openDataset),
 *  an image (getImageURL) or station dataset
 *
 *  that it knows how to open as a NetcdfDataset.
 * It annotates the NetcdfDataset with info from the InvDataset.
 *
 * Valid URLS:
 *  thredds:http://localhost:8080/test/addeStationDataset.xml#surfaceHourly (on-line)
 *  thredds://localhost:8080/test/addeStationDataset.xml#surfaceHourly (http is added)
 *  thredds:file:///c:/dev/netcdf-java-2.2/test/data/catalog/addeStationDataset.xml#AddeSurfaceData (absolute file)
 *  thredds:file:test/data/catalog/addeStationDataset.xml#AddeSurfaceData (reletive file)
 */
public class ThreddsDataFactory {
  static private String threddsServer = "http://motherlode.ucar.edu:8080/thredds/";
  static private boolean debugOpen = false;

  private InvCatalogFactory catFactory = new InvCatalogFactory("", false);
  private StringBuffer log;
  public String getErrorMessages() { return log == null ? "" : log.toString(); }

  static public void setDebugFlags(ucar.nc2.util.DebugFlags debugFlag) {
    debugOpen = debugFlag.isSet("thredds/debugOpen");
  }

  public NetcdfDataset openDataset( String location, ucar.nc2.util.CancelTask task) throws java.io.IOException {
    log = new StringBuffer();

    if (location.startsWith("thredds:"))
      location = location.substring(8);
    if (!location.startsWith("http:") && !location.startsWith("file:"))
      location = "http:" + location;

    InvCatalog catalog = null;
    InvDataset invDataset = null;
    String datasetId = null;

    // form 1 : catalog#datasetId
    int pos = location.indexOf('#');
    if (pos >= 0) {
      String catalogLocation = location.substring(0,pos);
      catalog = catFactory.readXML(catalogLocation);
      if (!catalog.check( log)) {
        return null;
      }

      datasetId = location.substring( pos+1);
      invDataset = catalog.findDatasetByID( datasetId);
      if (invDataset == null) {
        log.append("Could not find dataset "+datasetId+" in "+catalogLocation);
        return null;
      }
    } else {
      log.append("Must have the form catalog.xml#datasetId");
      return null;
    }

    return openDataset( invDataset, task);
  }

  public NetcdfDataset openDataset( InvDataset invDataset, ucar.nc2.util.CancelTask task) throws java.io.IOException {
    log = new StringBuffer();

    // now we have an InvDataset
    String datasetId = invDataset.getID();
    String title = invDataset.getName();

    List accessList = invDataset.getAccess(); // a list of all the accesses
    while (accessList.size() > 0) {
      InvAccess access = chooseDatasetAccess(accessList);

      // no valid access
      if (access == null) {
        log.append("No access that could be used in dataset "+invDataset);
        if (debugOpen) System.out.println(log.toString());
        return null;
      }

      String datasetLocation = access.getStandardUrlName();
      ServiceType serviceType = access.getService().getServiceType();
      if (debugOpen) System.out.println("ThreddsDataset.openDataset= "+datasetLocation);

      // deal with RESOLVER type
      if (serviceType == ServiceType.RESOLVER) {
        InvDatasetImpl rds = openResolver( datasetLocation);
        if (rds == null) return null;

        accessList = rds.getAccess();
        continue;

        //urlString = access.getStandardUrlName();
        //service = access.getService();
      }

      // ready to open it through netcdf API
      NetcdfDataset ds = null;

      // open DODS type
      if ((serviceType == ServiceType.OPENDAP) || (serviceType == ServiceType.DODS)) {
        try {
          DODSNetcdfFile dodsfile = new DODSNetcdfFile(datasetLocation, task);
          ds = new NetcdfDataset( dodsfile);
        } catch (DODSException e) {
          log.append("Cant open as OpenDAP "+datasetLocation);
          accessList.remove( access);
          continue;
        }
      }

      // open ADDE type
      else if (serviceType == ServiceType.ADDE) {
        try {
          ds = AddeDatasetFactory.openDataset(datasetLocation, task);

        } catch (IOException e) {
          log.append("Cant open as ADDE dataset= "+datasetLocation);
          accessList.remove( access);
          continue;
        }
      }

      else {
        // open through NetcdfDataset API
        ds = (NetcdfDataset) NetcdfDataset.openDataset( datasetLocation, true, task);
      }

      if (ds != null) {
        ds.setId(datasetId);
        ds.setTitle(title);
        annotate( invDataset, ds);
      }

      if (debugOpen) System.out.println(log.toString());
      return ds;
    } // loop over accesses

    if (debugOpen) System.out.println(log.toString());
    return null;
  }

  // see if theres an access method we can open through the netcdf API
  private InvAccess chooseDatasetAccess(List accessList) {

    // should mean that it can be opened through netcdf API
    InvAccess access = findAccessByServiceType( accessList, ServiceType.NETCDF); //  ServiceType.NETCDF is deprecated

    // file or HTTPServer, in a format we can read
    if (access == null)
      access = findAccessByServiceType( accessList, ServiceType.FILE);

    if (access == null) {
      InvAccess tryAccess = findAccessByServiceType( accessList, ServiceType.HTTPServer);
      if (tryAccess == null)
        tryAccess = findAccessByServiceType( accessList, ServiceType.HTTP); //  ServiceType.HTTP should be HTTPServer
      if (tryAccess != null) {
        DataFormatType format = tryAccess.getDataFormatType();
        if ((DataFormatType.NETCDF == format) || (DataFormatType.NCML == format))
          access = tryAccess;
      }
    }

    // OpenDAP
    if (access == null)
      access = findAccessByServiceType( accessList, ServiceType.DODS);
    if (access == null)
      access = findAccessByServiceType( accessList, ServiceType.OPENDAP);

      // ADDE
    if (access == null)
      access = findAccessByServiceType( accessList, ServiceType.ADDE);

      // RESOLVER
    if (access == null) {
      access = findAccessByServiceType( accessList, ServiceType.RESOLVER);
    }

    return access;
  }

  // works against the accessList instead of the dataset list, so we can remove and try again
  private InvAccess findAccessByServiceType( List accessList, ServiceType type) {
    for (int i = 0; i < accessList.size(); i++) {
      InvAccess a =  (InvAccess) accessList.get(i);
      if (type == a.getService().getServiceType());
        return a;
    }
    return null;
  }

  private InvDatasetImpl openResolver( String urlString) {
    try {
      InvCatalog catalog = catFactory.readXML( urlString);
      if (catalog == null) return null;
      StringBuffer buff = new StringBuffer();
      if (!catalog.check( buff)) {
        log.append("Invalid catalog  from Resolver <"+ urlString+">\n");
        log.append( buff.toString());
        return null;
      }
      InvDataset top = catalog.getDataset();
      if (top.hasAccess())
        return (InvDatasetImpl) top;
      else {
        java.util.List datasets = top.getDatasets();
        return (InvDatasetImpl) datasets.get(0);
      }

    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  public static void annotate( InvDataset ds, NetcdfDataset ncDataset) {
    ncDataset.setTitle( ds.getName());
    ncDataset.setId( ds.getID());

    // add properties as global attributes
    java.util.List list = ds.getProperties();
    for (int i=0; i<list.size(); i++) {
      InvProperty p = (InvProperty) list.get(i);
      String name = p.getName();
      if (null == ncDataset.findGlobalAttribute(name)) {
        ncDataset.addAttribute( null, new Attribute(name, p.getValue()));
      }
    }

    ThreddsMetadata.GeospatialCoverage geoCoverage = ds.getGeospatialCoverage();
    if (geoCoverage != null) {
      ArrayDouble.D1 data = new ArrayDouble.D1(4);
      data.set(0, geoCoverage.getLatStart());
      data.set(1, geoCoverage.getLonStart());
      data.set(2, geoCoverage.getLatExtent());
      data.set(3, geoCoverage.getLonExtent());
      ncDataset.addAttribute(null, new Attribute("LatLonBoundingBox", data));
    }

    /* ThreddsMetadata.TimeCoverage timeCoverage = ds.getTimeCoverage();
    if (geoCoverage != null) {
      ArrayDouble.D1 data = new ArrayDouble.D1(4);
      data.set(0, timeCoverage.getStart());
      data.set(1, timeCoverage.getEnd());
      ncDataset.addAttribute(null, new Attribute("TimeCoverage", data));
    } */

    ncDataset.finish();
  }

 //////////////////////////////////////////////////////////////////////////////////
 // station

  public InvAccess getStationAccess( InvDataset invDataset) {
    log = new StringBuffer();

    List accessList = invDataset.getAccess(); // a list of all the accesses
    if (accessList.size() > 0)
      return (InvAccess) accessList.get(0); // LOOK getStationAccess

    return null;
  }

 //////////////////////////////////////////////////////////////////////////////////
 // image

  public InvAccess getImageAccess( InvDataset invDataset) {
    log = new StringBuffer();

    List accessList = invDataset.getAccess(); // a list of all the accesses
    while (accessList.size() > 0) {
      InvAccess access = chooseImageAccess(accessList);
      if (access != null) return access;

      // next choice is resolver type.
      access = invDataset.getAccess( ServiceType.RESOLVER);

      // no valid access
      if (access == null) {
        log.append("No access that could be used in dataset "+invDataset);
        return null;
      }

      String datasetLocation = access.getStandardUrlName();
      ServiceType serviceType = access.getService().getServiceType();
      if (debugOpen) System.out.println("ThreddsDataset.openDataset= "+datasetLocation);

      // deal with RESOLVER type
      if (serviceType == ServiceType.RESOLVER) {
        InvDatasetImpl rds = openResolver( datasetLocation);
        if (rds == null) return null;

        accessList = invDataset.getAccess();
        continue;
      }

    } // loop over accesses

    return null;
  }

  // see if theres an access method we can open through the netcdf API
  private InvAccess chooseImageAccess(List accessList) {

    InvAccess access = null;

    access = findAccessByDataFormatType( accessList, DataFormatType.JPEG);
    if (access != null) return access;

    access = findAccessByDataFormatType( accessList, DataFormatType.GIF);
    if (access != null) return access;

    access = findAccessByDataFormatType( accessList, DataFormatType.TIFF);
    if (access != null) return access;

    access = findAccessByServiceType( accessList, ServiceType.ADDE);
    if (access != null) {
      String datasetLocation = access.getStandardUrlName();
      if (datasetLocation.indexOf("image") > 0)
        return access;
    }


    return access;
  }

  // works against the accessList instead of the dataset list, so we can remove and try again
  private InvAccess findAccessByDataFormatType( List accessList, DataFormatType type) {
    for (int i = 0; i < accessList.size(); i++) {
      InvAccess a =  (InvAccess) accessList.get(i);
      if (type == a.getDataFormatType());
        return a;
    }
    return null;
  }



}
