/*******************************************************************************
 * Copyright (c) 2018 Attila Kovacs <attila[AT]sigmyne.com>.
 * All rights reserved. 
 * 
 * This file is part of crush.
 * 
 *     crush is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     crush is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with crush.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Attila Kovacs <attila[AT]sigmyne.com> - initial API and implementation
 ******************************************************************************/

package crush.telescope.sofia;

import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import jnum.Util;
import jnum.fits.FitsToolkit;
import nom.tam.fits.Header;
import nom.tam.fits.HeaderCard;
import nom.tam.fits.HeaderCardException;
import nom.tam.util.Cursor;

public class SofiaProcessingData extends SofiaData {
    Set<String> associatedAORs, associatedMissionIDs;
    Set<Float> associatedFrequencies;
    String processLevel;
    String headerStatus;
    String softwareName, softwareFullVersion, productType, revision, quality; 
    int nSpectra = -1;
    int qualityLevel = defaultQuality;

    public SofiaProcessingData() {}

    public SofiaProcessingData(SofiaHeader header) {
        this();
        parseHeader(header);
    }

    public void parseHeader(SofiaHeader header) {
        processLevel = header.getString("PROCSTAT");
        headerStatus = header.getString("HEADSTAT");
        quality = header.getString("DATAQUAL");
        nSpectra = header.getInt("N_SPEC", -1);
        softwareName = header.getString("PIPELINE");
        softwareFullVersion = header.getString("PIPEVERS");
        productType = header.getString("PRODTYPE");
        revision = header.getString("FILEREV");
        
        String list = header.getString("ASSC_AOR");
        if(list == null) associatedAORs = null;
        else {
            associatedAORs = new TreeSet<>();
            StringTokenizer tokens = new StringTokenizer(list, ",");
            while(tokens.hasMoreTokens()) associatedAORs.add(tokens.nextToken().trim());
        }
        
        list = header.getString("ASSC_MSN");
        if(list == null) associatedMissionIDs = null;
        else {
            associatedMissionIDs = new TreeSet<>();
            StringTokenizer tokens = new StringTokenizer(list, ",");
            while(tokens.hasMoreTokens()) associatedMissionIDs.add(tokens.nextToken().trim());
        }
        
        list = header.getString("ASSC_FRQ");
        if(list == null) associatedFrequencies = null;
        else {
            associatedFrequencies = new TreeSet<>();
            StringTokenizer tokens = new StringTokenizer(list, " \t,");
            while(tokens.hasMoreTokens()) associatedFrequencies.add(Float.parseFloat(tokens.nextToken()));
        }
            
        
        qualityLevel = defaultQuality;
        if(quality != null) for(int i=qualityNames.length; --i >= 0; ) if(quality.equalsIgnoreCase(qualityNames[i])) {
            qualityLevel = i;
            break;
        }
    }

    @Override
    public void editHeader(Header header) throws HeaderCardException {
        Cursor<String, HeaderCard> c = FitsToolkit.endOf(header);
        
        c.add(new HeaderCard("COMMENT", "<------ SOFIA Processing Information ------>", false));
        int level = 0;
        if(processLevel != null) if(processLevel.toUpperCase().startsWith("LEVEL_")) {
            try { level = Integer.parseInt(processLevel.substring(6)); }
            catch(NumberFormatException e) {}
        }

        if(processLevel != null) c.add(makeCard("PROCSTAT", processLevel, getComment(level)));
        if(headerStatus != null) c.add(makeCard("HEADSTAT", headerStatus, "Header state."));
        if(softwareName != null) c.add(makeCard("PIPELINE", softwareName, "Software that created this file."));
        if(softwareFullVersion != null) c.add(makeCard("PIPEVERS", softwareFullVersion, "Full software version info."));
        if(productType != null) c.add(makeCard("PRODTYPE", productType, "Type of product."));
        if(revision != null) c.add(makeCard("FILEREV", revision, "File revision identifier."));
        if(quality != null) c.add(makeCard("DATAQUAL", quality, "Data quality level."));
        if(nSpectra >= 0) c.add(makeCard("N_SPEC", nSpectra, "Number of spectra included."));
        
        if(associatedAORs != null) if(!associatedAORs.isEmpty())
            FitsToolkit.addLongKey(c, "ASSC_AOR", toString(associatedAORs), "Associated AOR IDs.");
        
        if(associatedMissionIDs != null) if(!associatedMissionIDs.isEmpty()) 
            FitsToolkit.addLongKey(c, "ASSC_MSN", toString(associatedMissionIDs), "Associated Mission IDs.");
        
        if(associatedFrequencies != null) if(!associatedFrequencies.isEmpty())
            FitsToolkit.addLongKey(c, "ASSC_FRQ", toString(associatedFrequencies), "Associated Frequencies.");
    }

    @Override
    public String getLogID() {
        return "proc";
    }

    @Override
    public Object getTableEntry(String name) {
        if(name.equals("q")) return qualityLevel;
        else if(name.equals("nspec")) return nSpectra;
        else if(name.equals("quality")) return quality;
        else if(name.equals("level")) return processLevel;
        else if(name.equals("stat")) return headerStatus;
        else if(name.equals("product")) return productType;
        
        return super.getTableEntry(name);
    }

    public void addAssociatedAOR(String id) {
        if(associatedAORs == null) associatedAORs = new TreeSet<>();
        associatedAORs.add(id);
    }
    
    public void addAssociatedMissionID(String id) {
        if(associatedMissionIDs == null) associatedMissionIDs = new TreeSet<>();
        associatedMissionIDs.add(id);
    }
    
    public void addAssociatedFrequency(double value) {
        if(associatedFrequencies == null) associatedFrequencies = new TreeSet<>();
        associatedFrequencies.add((float) value);
    }
    
    public static String getComment(int level) {
        if(level < 0 || level > 4) return "Invalid processing level: " + level;
        return processLevelComment[level];
    }

    private final static String[] processLevelComment = {
            "Unknown processing level", 
            "Raw engineering/diagnostic data.",
            "Raw uncalibrated science data.",
            "Corrected/reduced science data.",
            "Flux-calibrated science data.",
            "Higher order product (e.g. composites)."
    };

    
    public static String toString(Set<?> set) {
        if(set.isEmpty()) return null;
        
        Object[] array = set.toArray();
          
        StringBuffer buf = new StringBuffer();
        buf.append(array[0]);

        for(int i=1; i<array.length; i++) if(array[i] != null) {
            Object o = array[i];
           
            if(o instanceof Short) { if((Short) o == UNKNOWN_INT_VALUE) continue; }
            else if(o instanceof Integer) { if((Integer) o == UNKNOWN_INT_VALUE) continue; }
            else if(o instanceof Long) { if((Long) o == UNKNOWN_INT_VALUE) continue; }
            else if(o instanceof Float) { if((Float) o == UNKNOWN_FLOAT_VALUE) continue; }
            else if(o instanceof Double) { if((Double) o == UNKNOWN_DOUBLE_VALUE) continue; }
            else if(o instanceof String) { if(Util.equals(o, UNKNOWN_STRING_VALUE)) continue; }
            
            buf.append(", ");
            buf.append(o);
        }

        return new String(buf);
    }
    
 
    
    public static String getLevelName(int level) {
        return "LEVEL_" + level;
    }
    
    protected String getProductType(int dims) {
        switch(dims) {
        case 0: return "HEADER";
        case 1: return "1D";
        case 2: return "IMAGE";
        case 3: return "CUBE";
        }
        return dims > 0 ? dims + "D" : "UKNOWN";
    }

    
    public static class CRUSH extends SofiaProcessingData {
      
        public CRUSH(boolean isCalibrated, int dims, int qualityLevel) {
            processLevel = getLevelName(isCalibrated ? 3 : 2);
            headerStatus = SofiaProcessingData.MODIFIED;
            softwareName = "crush";
            softwareFullVersion = crush.CRUSH.getFullVersion();
            productType = "CRUSH-" + getProductType(dims);
            this.qualityLevel = qualityLevel; 
        } 
        
    }
    
    
    public static String FAIL = "FAIL";
    public static String PROBLEM = "PROBLEM";
    public static String TEST = "TEST";
    public static String USABLE = "USABLE";
    public static String NOMINAL = "NOMINAL";
    
    public static String ORIGINAL = "ORIGINAL";
    public static String MODIFIED = "MODIFIED";
    public static String CORRECTED = "CORRECTED";
    
    public static String qualityNames[] = { FAIL, PROBLEM, TEST, USABLE, NOMINAL };
    public static int defaultQuality = qualityNames.length - 1;
}
