package usda.weru.weps.wepsRunControl;

import csip.api.server.ServiceException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import usda.weru.util.About;
import usda.weru.util.ConfigData;
import usda.weru.util.WepsMessage;
import static usda.weru.util.WepsMessage.MessageSeverity.ERROR;
import static usda.weru.weps.wepsRunControl.WrcException.wrcExceptionType.stationNotFound;

/**
 *
 * @author mhaas
 */
public class WrcRunModelClimate extends WrcRunModel {
    
    double lat;
    double lon;
    File workspaceDir;
    String stateId;
    String stationId;
    
    public WrcRunModelClimate () {
        this("", null, null, null);
    }
        
    public WrcRunModelClimate (String runDir, Logger log, WrcBackgroundIf bgIf) {
        super (runDir, log, bgIf);
        
        lat = 0.;
        lon = 0.;
        stateId = "";
        stationId = "";
        
        addCmds_exe(ConfigData.getDefault().getDataParsed(ConfigData.CliExe));
        addCmds_fromStr(ConfigData.getDefault().getDataParsed(ConfigData.CliCmd));

        dlgIdx = 1;
        
        emptyCmdsMsg = "Climate file supplied";

        rtnVal += 4;  //debugging only
    }
    
    public WrcRunModelClimate (String runDir, String[] commandsArr, Logger log, WrcBackgroundIf bgIf) {
        super (runDir, commandsArr, log, bgIf);
    }
    
    @Override
    public Integer execModel() throws Exception {
        if (skipExec) {
            rtnVal = 0;
        } else {
            try {
                rtnVal = super.execModel();
            } catch (Exception ex) {
                throw checkExecErrors (ex);
            }
        }
        return rtnVal;
    }
    
    protected Exception checkExecErrors (Exception ex) {
        if (ex instanceof WrcException) {
            WrcException wrcEx = (WrcException)ex;
            // if the error is from CSIP, the error will have a cause with the specific info in it.
            if (wrcEx.getCause() != null) {
                String s = wrcEx.getCause().getMessage();
                int i1 = s.indexOf("\"error\"");
                if (i1 > -1) {
                    int i2 = s.indexOf('"', i1+8);
                    int i3 = s.indexOf('"', i1+10);
                    String s2 = s.substring(i2+1, i3);
                    if (s2.contains("station not found") && s2.contains("US_cligen_stations")) {
                        if (s2.contains("US_cligen_stations")) {
                            return new WrcException ("Cligen Station not found in cligen database.\nSelect another cligen station and retry the run.", ex).
                                       setFatal(false).
                                       setType(stationNotFound);
                        }
                    }
                }
            }
            // if the error is from local, the error will have a log with the specific info in it.
            else {
                for (WepsMessage msg : localWepsMsgLog.getMessages(ERROR)) {
                    String s = msg.getMessage();
                    if (s.contains("Selected cligen station does not exist")) {
                        return new WrcException ("Cligen Station not found in cligen database.\nSelect another cligen station and retry the run.", ex).
                                   setFatal(false).
                                   setType(stationNotFound);
                    }
                }
            }
        }

        return ex;
    }
    
    public void addCmds_stateNum (Long state) {
        stateId = state.toString();
        addCmd("-S", stateId);
    }
    
    public void addCmds_stationNum (Long station) {
        stationId = station.toString();
        addCmd("-s", stationId);
    }
    
    public void addCmds_startYear (String startYear) {
        addCmd("-b", startYear);
    }
    
    public void addCmds_totalYears (String totalYears) {
        addCmd("-y", totalYears);
    }
    
    public void addCmds_outputFile (String path) {
        addCmd("-o", path);
    }
    
    public void addCmds_latlon (String latlon) {
        addCmds_latlon (latlon, true);
    }
    
    private void addCmds_latlon (String latlon, boolean setCmd) {
        // ex: "+37.73835;-100.43785"
        int i = latlon.indexOf(';');
        lat = Double.valueOf(latlon.substring(0, i));
        lon = Double.valueOf(latlon.substring(i+1));
        
        if (setCmd) {
            addCmd("-L", latlon);
        }
    }
    
    public boolean isServerPrismMode () {
        return (ConfigData.getDefault().getData("CD-SC-cligen-execOnServer").contains("1")  &&
                ConfigData.getDefault().getData("CD-SC-cligen-endpoint").contains("cligen_prism") );
    }
    
    public boolean isLocalPrismMode () {
        return (ConfigData.getDefault().getData("CD-SC-button-cligenPrismMode").toLowerCase().contains("prism")  &&
               !ConfigData.getDefault().getData("CD-SC-cligen-execOnServer").contains("1") &&
                ConfigData.getDefault().getData("CD-SC-cligen-doLocalPrismAdj").contains("1") );
    }
    
    public boolean isContinentalUS () {
        return isContinentalUS(stateId);
    }
    
    static public boolean isContinentalUS (String stateIdStr) {
        try {
            return isContinentalUS(Integer.parseInt(stateIdStr));
        } catch (NumberFormatException ex) {
        }
        return false;
    }
    
    static public boolean isContinentalUS (int stateNum) {
        return (stateNum < 49);
    }
    
    public void  addCmds_localPrism (String latlon) throws WrcException {
        
        addCmds_latlon (latlon, false);
        
        File cligenPrismWorking = new File (runDir);
        
        CligenWepsUtils cligenUtils = new CligenWepsUtils(lat, lon, cligenPrismWorking);
        
        String parFileName = "";
        int modelCmdListIdx = -1;
        
        for (String parm : modelCmdList) {
            if (parm.startsWith("-i")) {
                modelCmdListIdx = modelCmdList.indexOf(parm);
                parFileName = parm.substring(2);
                break;
            }
        }        
        if (parFileName.isEmpty()) {
            parFileName = ConfigData.getDefault().getData("CD-cligen database");
        }

        String adjustedFileName = makeLocalPrismCligenRecord(cligenUtils, parFileName, lat, lon);

        if (modelCmdListIdx > -1) {
            modelCmdList.set(modelCmdListIdx, "-i" + adjustedFileName);
        } else {
            modelCmdList.add("-i" + adjustedFileName);
        }
    }
    
    protected String makeLocalPrismCligenRecord (CligenWepsUtils cligenUtils, String parFileName, double lat, double lon) throws WrcException {

       
        try {
            File prismFile = findCachedPrismFile (lat, lon);
            JSONArray prismData = getPrismData (prismFile);
        
            String recordStr = cligenUtils.extractClimateRecord(parFileName, Integer.valueOf(stateId), Integer.valueOf(stationId));
            String adjustedFileName = cligenUtils.addPrismDataToRecord(new File(recordStr), prismData);
            return adjustedFileName;
        } catch (WrcException ex) {
            throw new WrcException ("Failed to get Prism data for cligen record.", ex).setFatal(false).setType(WrcException.wrcExceptionType.prismDataReadFail);
        } catch (ServiceException ex) {
            String m = ex.getMessage().toLowerCase();
            
            if (m.contains("index")) {
                throw new WrcException ("Failed to add Prism data to cligen record.", ex)
                        .setFatal(true).setType(WrcException.wrcExceptionType.indexFileError);
            } else if (m.contains(" par ")) {
                throw new WrcException ("Failed to add Prism data to cligen record.", ex)
                        .setFatal(true).setType(WrcException.wrcExceptionType.parFileError);
            }
            throw new WrcException ("Failed to add Prism data to cligen record.", ex).setFatal(false).setType(WrcException.wrcExceptionType.prismDataReadFail);
        }
    }
    
    protected File findCachedPrismFile (double lat, double lon) throws WrcException {
        File prismCacheDir = new File (About.getWepsCacheDir()+"/Prism");
        File prismFile = null;
        
        double avgDif = 100;
        File nearestFile = null;
        
        for (File f : prismCacheDir.listFiles()) {
            String name = f.getName();
            if (name.startsWith("prismCell")) {
                //lon / lat separator in file name is 2nd period
                int separator = name.indexOf('.',  name.indexOf('.')+1);
                double lonFile = Double.valueOf(name.substring(9,separator));
                double latFile = Double.valueOf(name.substring(separator+1, name.indexOf(".json")));

                double threshold = .05;

                double dif1 = (Math.abs(lat - latFile));
                double dif2 = (Math.abs(lon - lonFile));

                if ( (Math.abs(lat - latFile) < threshold) &&
                     (Math.abs(lon - lonFile) < threshold) ) {
                    
                    double avgDifThis = ( Math.abs(lat - latFile) + Math.abs(lon - lonFile) ) / 2.;
                    if (avgDifThis < avgDif) {
                        avgDif = avgDifThis;
                        prismFile = f;
                    }
                }
            }
        }
                
        if (prismFile == null  ||  !prismFile.exists()) {
            throw new WrcException ("Failed to find a cached prism file for this location: "+lat+lon);
        }
        return prismFile;
    }
    
    JSONArray getPrismData (File prismFile) throws WrcException {
        JSONObject jsonData;
        JSONArray resArray = null;
        
        try {
            String JSONStr = FileUtils.readFileToString(prismFile, (Charset)null);
            jsonData = new JSONObject (JSONStr);
            resArray = jsonData.getJSONArray("periods");
        } catch (FileNotFoundException ex) {
            throw new WrcException ("Failed to open prism data file.", ex);
        } catch (IOException ex) {
            throw new WrcException ("Failed to read prism data file.", ex);
        } catch (JSONException ex) {
            throw new WrcException ("Failed to create prism data json.", ex);
        }
        return resArray;
    }
}
