/*
 * Copyright 2016 davidstrawn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package strawn.evariant.rainsorter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.opengis.feature.simple.SimpleFeature;
import strawn.evariant.rainsorter.data.msapop.MSAPopulationLoader;
import strawn.evariant.rainsorter.data.msapop.MSAPopulationRecord;
import strawn.evariant.rainsorter.data.msashapefile.MSAShapefileLoader;
import strawn.evariant.rainsorter.data.precipitation.PrecipitationLoader;
import strawn.evariant.rainsorter.data.precipitation.PrecipitationRecord;
import strawn.evariant.rainsorter.data.qclcdstations.QCWeatherStationLoader;
import strawn.evariant.rainsorter.data.qclcdstations.QCWeatherStationRecord;
import strawn.evariant.rainsorter.exceptions.InvalidDataException;
import strawn.evariant.rainsorter.tools.GeometryTools;

/**
 *
 * @author davidstrawn
 * 
 * This class is the main workhorse of rainsorter. 
 * All needed data sets meet here, and are connected in a meaningful way
 * This class computes the population wetness.
 */
public class RainsorterEngine {
    
    HashMap<String, WeatherStation> wbanToStationMap;
    List<MetropolitanStatisticalArea> metroAreas;
    List<SimpleFeature> regions;
    HashMap<String, MetropolitanStatisticalArea> msaMap;
    
    /**
     * Convenience method for loading up an Engine. 
     * Loads all data sets from their loaders and calls the constructor
     * 
     * @return Completed RainsorterEngine
     * @throws InvalidDataException - See constructor for details on why this is thrown
     * @throws IOException - Thrown if data was unable to be loaded from disk
     */
    public static RainsorterEngine createEngine() throws InvalidDataException, IOException {
        return new RainsorterEngine(
            MSAPopulationLoader.loadRecordsFromDisk(),
            QCWeatherStationLoader.loadRecordsFromDisk(),
            MSAShapefileLoader.loadFeatures(),
            PrecipitationLoader.loadRecordsFromDisk());
    }
    
    /**
     * Main constructor, requires all relevant data sets to be passed in at time of construction
     * 
     * @param populations List of MSAPopulationRecord generated by MSAPopulationLoader
     * @param stationRecords List of QCWeatherStationRecord generated by QCWeatherStationLoader
     * @param regions List of SimpleFeature generated by MSAShapefileLoader
     * @param precipitation List of PrecipitationRecord generated by PrecipitationLoader
     * @throws InvalidDataException Thrown if a precipitation record cannot be matched to a station, or if an MSA region definition cannot be mapped to an MSA
     */
    public RainsorterEngine(List<MSAPopulationRecord> populations, List<QCWeatherStationRecord> stationRecords, List<SimpleFeature> regions, List<PrecipitationRecord> precipitation) throws InvalidDataException {
        //store the list of Features(Regions)
        this.regions = regions;
        //create the MetropolitanStatisticalAreas from the MSAPopulationRecord records
        metroAreas = DataOrganizationMethods.createMSAList(populations);
        //create a map of the MSAs keyed by CBSA code
        msaMap = DataOrganizationMethods.createCBSACodeToMSAMap(metroAreas);
        //create a map of weather stations, keyed by WBAN
        wbanToStationMap = DataOrganizationMethods.createWBANToStationMap(stationRecords);
        //add geotools regions to MSAs
        addRegionsToMSAs();
        //map stations to MSAs
        addStationsToRegions();
        //add precipitation data to stations
        addPrecipitationDataToStations(precipitation);
        //drop stations with no data
        dropAllEmptyStations();
        //calculate population wetness of each MSA
        calculatePopulationWetness();
        //sort regions by population wetness
        Collections.sort(metroAreas, new MSAWetnessComparator());
    }
    
    /**
     * 
     * @return A list of MSAs sorted by population wetness
     */
    public List<MetropolitanStatisticalArea> getSortedMSAs() {
        return metroAreas;
    }
    
    /**
     * Add each Region to the appropriate MSA, using the CBSA code which is included in the Region data
     * 
     * @throws InvalidDataException 
     */
    private void addRegionsToMSAs() throws InvalidDataException {
        for(SimpleFeature feature : regions) {
            String featureCBSACode = feature.getAttribute("CBSAFP").toString();
            MetropolitanStatisticalArea msa = msaMap.get(featureCBSACode);
            if(msa != null) {
                msa.addRegionFeature(feature);
            }else {
                throw new InvalidDataException("addRegionsToMSAs - No MSA found for CBSA Code:" + featureCBSACode);
            }
        }
    }
    
    /**
     * Uses the lat/long coordinates of a Weather Station to find the Region in which it belongs.
     * 
     * //TODO: This is slow, O(N^2) - we could write a faster algo by organizing the regions by boundary
     * but there are only ~400 MSAs and ~2500 stations, so this should go relatively fast
     * 
     * @throws InvalidDataException 
     */
    private void addStationsToRegions() throws InvalidDataException {
        for(WeatherStation station : wbanToStationMap.values()) {
            for(SimpleFeature feature : regions) {
                String featureCBSACode = feature.getAttribute("CBSAFP").toString();
                if(GeometryTools.isPointInRegion(station.longitude, station.latitude, feature)) {
                    MetropolitanStatisticalArea msa = msaMap.get(featureCBSACode);
                    if(msa != null) {
                        msa.addStation(station);
                    }else {
                        throw new InvalidDataException("addStationsToRegions - No MSA found for CBSA Code:" + featureCBSACode);
                    }
                }
            }
        }
    }
    
    /**
     * Iterates through a list of precipitation records, and adds them to the appropriate weather station based on WBAN code 
     * @param precipitation
     * @throws InvalidDataException If a station cannot be found with the appropriate WBAN code
     */
    private void addPrecipitationDataToStations(List<PrecipitationRecord> precipitation) throws InvalidDataException {
        for(PrecipitationRecord record : precipitation) {
            WeatherStation station = wbanToStationMap.get(record.wbanCode);
            if(station != null) {
                
            }else {
                throw new InvalidDataException("No station found for WBAN Code:" + record.wbanCode);
            }
        }
    }
    
    /**
     * Some stations have no precipitation date recorded from them. This removes all stations that do not have data
     * from the MSA they are assigned to(if any)
     */
    private void dropAllEmptyStations() {
        for(MetropolitanStatisticalArea msa : metroAreas) {
            msa.dropEmptyStations();
        }
    }
    
    /**
     * Iterates over all MSAs and has them compute population wetness
     */
    private void calculatePopulationWetness() {
        for(MetropolitanStatisticalArea msa : metroAreas) {
            msa.calculatePopulationWetness();
        }
    }
    
}

@@ -3166,67 +3166,8 @@
 tion
-, or if an MSA region definition cannot be mapped to an MSA
 %0A   
@@ -4799,36 +4799,8 @@
 As()
- throws InvalidDataException
  %7B%0A 
@@ -5081,144 +5081,8 @@
 e);%0A
-            %7Delse %7B%0A                throw new InvalidDataException(%22addRegionsToMSAs - No MSA found for CBSA Code:%22 + featureCBSACode);%0A
     

remove constraint that region must have MSA