package jobs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.collection.FilteringSimpleFeatureCollection;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.hibernatespatial.readers.Feature;
import org.onebusaway.gtfs.impl.GtfsDaoImpl;
import org.onebusaway.gtfs.serialization.GtfsReader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.geotools.data.FileDataStoreFinder;
import com.mchange.v2.c3p0.impl.DbAuth;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.operation.linemerge.LineMerger;
import models.gis.GisRoute;
import models.gis.GisStop;
import models.gis.GisUpload;
import models.gis.GisUploadField;
import models.gis.GisUploadType;
import models.gtfs.GtfsSnapshotMerge;
import models.gtfs.GtfsSnapshotMergeTask;
import models.gtfs.GtfsSnapshotMergeTaskStatus;
import models.transit.Agency;
import models.transit.Route;
import models.transit.ServiceCalendar;
import models.transit.ServiceCalendarDate;
import models.transit.Stop;
import models.transit.StopTime;
import models.transit.TripShape;
import models.transit.Trip;
import play.Logger;
import play.Play;
import play.jobs.Job;
import play.jobs.OnApplicationStart;
import utils.FeatureAttributeFormatter;
public class ProcessGisUpload extends Job {
  private Long _gisUploadId;
  
  public ProcessGisUpload(Long gisUploadId)
  {
    this._gisUploadId = gisUploadId;
  }
  
  public void doJob() {
    
    String uploadName = "gis_" + this._gisUploadId;
    
    File uploadedFile = new File(Play.configuration.getProperty("application.publicGisDataDirectory"), uploadName + ".zip");
    
    File outputPath = new File(Play.configuration.getProperty("application.publicGisDataDirectory"), uploadName);
       
       
        try
        {    
          GisUpload gisUpload = null;
          
          while(gisUpload == null)
          {
            gisUpload = GisUpload.findById(this._gisUploadId);
            Thread.sleep(1000);
            
            Logger.info("Waiting for gisUpload object...");
          }
          
          File shapeFile = null;
          
          // unpack the zip if needed
          if(!outputPath.exists())
          {
            outputPath.mkdir();
            
            FileInputStream fileInputStream = new FileInputStream(uploadedFile);              
              ZipInputStream zipInput = new ZipInputStream(fileInputStream); 
              
              ZipEntry zipEntry = null; 
             
              while ((zipEntry = zipInput.getNextEntry()) != null) 
              {         
                  if(zipEntry.isDirectory()) 
                  {
                    Logger.info("Unexpected directory: ", zipEntry.getName()); 
                  }
                  else 
                  {
                    Logger.info("Unzipping", zipEntry.getName()); 
                    
                    File entryFile = new File(outputPath, zipEntry.getName());
                    
                      FileOutputStream unzippedFileOut = new FileOutputStream(entryFile);
                      
                      int length;
                      byte[] buffer = new byte[1000];
                      
                      while ((length = zipInput.read(buffer))>0) 
                      {
                        unzippedFileOut.write(buffer, 0, length);
                      }
                      
                      zipInput.closeEntry(); 
                      unzippedFileOut.close(); 
                  } 
              }
             
              zipInput.close();
          }
          
          // find the shapefile
          for(File dirFile : outputPath.listFiles())
        {
          if(FilenameUtils.getExtension(dirFile.getName()).toLowerCase().equals("shp"))
              {
                if(shapeFile == null)
                  shapeFile = dirFile;
                else
                  Logger.warn("Zip contains more than one shapefile--ignoring others.");
              }
        }          
            
          // (re)load the shapefile data 
            if(shapeFile != null)
            {
              
              // remove existing imports
              if(gisUpload.type == GisUploadType.ROUTES)
              {
                List<GisRoute> routes = GisRoute.find("gisUpload = ?", gisUpload).fetch();
                
                for(GisRoute route : routes)
                {
                  route.clear();
                  route.delete();
                }  
              }
              else if(gisUpload.type == GisUploadType.STOPS)
                GisStop.delete("gisUpload = ?", gisUpload);
              
              // remove existing updload field mappings
              GisUploadField.delete("gisUpload = ?", gisUpload);
              
              FileDataStore store = FileDataStoreFinder.getDataStore(shapeFile);
              SimpleFeatureSource featureSource = store.getFeatureSource();
              
              SimpleFeatureCollection featureCollection = featureSource.getFeatures();
              SimpleFeatureIterator featureIterator = featureCollection.features();
              
              List<AttributeDescriptor> attributeDescriptors = featureSource.getSchema().getAttributeDescriptors();
              
              // update field listing
              Long position = new Long(0);
              
              for(AttributeDescriptor attribute : attributeDescriptors)
              {
                GisUploadField field = new GisUploadField();
                field.fieldName = attribute.getName().toString();
                field.fieldType = attribute.getType().getName().getLocalPart();
                field.fieldPosition = position;
                
                field.gisUpload = gisUpload;
                
                field.save();
           
                position++;
              }
              
                
              CoordinateReferenceSystem dataCRS = featureSource.getSchema().getCoordinateReferenceSystem();
             
              String code = "EPSG:4326";
              CRSAuthorityFactory crsAuthorityFactory = CRS.getAuthorityFactory(true);
              CoordinateReferenceSystem mapCRS = crsAuthorityFactory.createCoordinateReferenceSystem(code);
                
                
              boolean lenient = true; // allow for some error due to different datums
              MathTransform transform = CRS.findMathTransform(dataCRS, mapCRS, lenient);
              
              while (featureIterator.hasNext()) 
              {
                SimpleFeature feature = featureIterator.next();
    
                GeometryType geomType = feature.getFeatureType().getGeometryDescriptor().getType();
                
                // handle appropriate shape/upload type
                if(gisUpload.type == GisUploadType.ROUTES)
                {  
                  if(geomType.getBinding() != MultiLineString.class)
                  {
                    Logger.error("Unexpected geometry type: ", geomType);
                    continue;
                  }
                
                  MultiLineString multiLineString = (MultiLineString)JTS.transform((Geometry)feature.getDefaultGeometry(), transform);
                  
                
                  GisRoute route = new GisRoute();
                    
                    route.gisUpload = gisUpload;
                    route.agency = gisUpload.agency;
                    route.oid = feature.getID();
                    route.originalShape = multiLineString;
                    route.originalShape.setSRID(4326);
                    
                    if(gisUpload.fieldName != null)
                  {
                    FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldName);
                    route.routeName =  attribFormatter.format(feature);
                    
                  }
                  if(gisUpload.fieldId != null)
                {
                  FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldId);
                  route.routeId =  attribFormatter.format(feature);
                }
                if(gisUpload.fieldDescription != null)
                {
                  FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldDescription);
                  route.description =  attribFormatter.format(feature);
                }
                  
                  route.save();
                  
                  route.processSegments();
                  
                   }
                else if(gisUpload.type == GisUploadType.STOPS)
                {
                  if(geomType.getBinding() != Point.class)
                  {
                    Logger.error("Unexpected geometry type: ", geomType);
                    continue;
                  }
                
                  GisStop stop = new GisStop();
                    
                  stop.gisUpload = gisUpload;
                  stop.agency = gisUpload.agency;
                  stop.oid = feature.getID();
                  stop.shape = (Point)JTS.transform((Geometry)feature.getDefaultGeometry(), transform);
                  stop.shape.setSRID(4326);
                    
                  if(gisUpload.fieldName != null)
                  {
                    FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldName);
                    stop.stopName =  attribFormatter.format(feature);
                  }
                  if(gisUpload.fieldId != null)
                  {
                    FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldId);
                    stop.stopId =  attribFormatter.format(feature);
                  }
                  if(gisUpload.fieldDescription != null)
                  {
                    FeatureAttributeFormatter attribFormatter = new FeatureAttributeFormatter(gisUpload.fieldDescription);
                    stop.description =  attribFormatter.format(feature);
                  }
                  
                    stop.save();
                }
              }  
            }
            else
            {
              Logger.error("Zip didn't contain a valid shapefile.");
            }
        }
        catch(Exception e)
        {  
          Logger.error("Unable to process GIS Upload: ", e.toString());
          e.printStackTrace();
        }
  }
}