/*******************************************************************************
 * Copyright (c) 2016 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.apex;

import crush.*;
import crush.sourcemodel.ChopNodPhotometry;
import crush.telescope.Mount;
import crush.telescope.TelescopeInstrument;
import jnum.Unit;
import jnum.astro.EquatorialCoordinates;
import nom.tam.fits.*;

import java.io.*;
import java.util.List;
import java.util.Vector;


public abstract class APEXInstrument<ChannelType extends Channel> extends TelescopeInstrument<ChannelType> {
    /**
     * 
     */
    private static final long serialVersionUID = 4654355516742128832L;

    private int maxFeeds;
    int pixelChannels = 1;

    public int[] activeBands;
    public int band;
    
    
    String sideband = null;
    double frequencyResolution; // Hz
    

    public APEXInstrument(String name, int size) {
        super(name, size);
    }

    public APEXInstrument(String name) {
        super(name);
    }

    
    @Override
    public String getTelescopeName() {
        return "APEX";
    }

    @Override
    public APEXInstrument<ChannelType> copy() {
        APEXInstrument<ChannelType> copy = (APEXInstrument<ChannelType>) super.copy();
        return copy;
    }

    
    @Override
    protected APEXLayout getLayoutInstance() { return new APEXLayout(this); }
    
    @Override
    public APEXLayout createLayout() { return (APEXLayout) super.createLayout(); }
    
    @Override
    public APEXLayout getLayout() { return (APEXLayout) super.getLayout(); }
    
    public int getReferencePixelFixedIndex() { return getLayout().getReferencePixelFixedIndex(); }
    

    public void readPar(String fileName) throws IOException, FitsException {
        try(Fits fits = new Fits(new File(fileName), fileName.endsWith(".gz"))) {

            BinaryTableHDU hdu = (BinaryTableHDU) fits.getHDU(1);
            readPar(hdu);

            fits.close();
        }
    }

    public final void readPar(Fits fits) throws IOException, FitsException {
        readPar((BinaryTableHDU) fits.getHDU(1));
        fits.close();
    }

    
    public void readPar(BinaryTableHDU hdu) throws IOException, FitsException {
        Header header = hdu.getHeader();

        maxFeeds = header.getIntValue("FEBEFEED", 1);
        
        Object[] data = hdu.getRow(0);
       
        activeBands = (int[]) data[hdu.findColumn("USEBAND")];
        
        // Read in the instrument location...
        String cabin = header.getStringValue("DEWCABIN").toLowerCase();
        if(cabin.startsWith("c")) mount = Mount.CASSEGRAIN;
        else if(cabin.startsWith("a") || cabin.equals("nasmyth_a")) mount = Mount.LEFT_NASMYTH;
        else if(cabin.startsWith("b") || cabin.equals("nasmyth_b")) mount = Mount.RIGHT_NASMYTH;
        else throw new IllegalStateException("Instrument cabin undefined.");

        info("Instrument mounted in " + mount.name + " cabin.");
        
        createLayout().parseHDU(hdu);   
    }
    
    public void parseArrayDataHeader(Header header) {
        pixelChannels = header.getIntValue("CHANNELS");
        storeChannels = maxFeeds * pixelChannels;
        band = header.getIntValue("BASEBAND", 0);
        frequencyResolution = header.getDoubleValue("FREQRES", 0.0) * Unit.Hz;
        sideband = header.getStringValue("SIDEBAND");
    }

    @Override
    public APEXScan<? extends APEXSubscan<?>> getScanInstance() {
        return new APEXScan<APEXSubscan<?>>(this);
    }
    

    @Override
    public void validate(Vector<Scan<?>> scans) throws Exception {

        final APEXScan<?> firstScan = (APEXScan<?>) scans.get(0);
        final APEXSubscan<?> firstSubscan = firstScan.get(0);
        final EquatorialCoordinates reference = firstScan.equatorial;
        final String sourceName = firstScan.getSourceName();

        final double pointingTolerance = getPointSize() / 5.0;
        final boolean isChopped = firstSubscan.getChopper() != null;


        if(isChopped) {
            info("Chopped photometry reduction mode.");
            info("Target is [" + sourceName + "] at " + reference);
            setOption("chopped");
        }
        else if(sourceName.equalsIgnoreCase("SKYDIP")) {
            info("Skydip reduction mode.");
            setOption("skydip");

            if(scans.size() > 1) {
                info("Ignoring all but first scan in list (for skydip).");
                scans.clear();
                scans.add(firstScan);
            }
        }

        if(firstScan.type.equalsIgnoreCase("POINT")) if(scans.size() == 1) firstScan.setSuggestPointing();

        if(hasOption("nochecks")) return;

        // Make sure the rest of the list conform to the first scan...
        for(int i=scans.size(); --i > 0; ) {
            APEXScan<?> scan = (APEXScan<?>) scans.get(i);
            APEXSubscan<?> subscan = scan.get(0);

            // Throw out any subsequent skydips...
            if(scan.getSourceName().equalsIgnoreCase("SKYDIP")) {
                warning("Scan " + scan.getID() + " is a skydip. Dropping from dataset.");
                scans.remove(i);
            }

            boolean subscanChopped = subscan.getChopper() != null;

            if(subscanChopped != isChopped) {	
                if(isChopped) warning("Scan " + scan.getID() + " is not a chopped scan. Dropping from dataset.");
                else warning("Scan " + scan.getID() + " is a chopped scan. Dropping from dataset.");
                scans.remove(i);
                continue;
            }

            if(isChopped) {
                if(!scan.isNonSidereal) {
                    if(scan.equatorial.distanceTo(reference) > pointingTolerance) {
                        warning("Scan " + scan.getID() + " observed at a different position. Dropping from dataset.");
                        CRUSH.suggest(this, "           (You can use 'moving' to keep and reduce anyway.)");
                        scans.remove(i);
                    }
                }
                else if(!scan.getSourceName().equalsIgnoreCase(sourceName)) {
                    warning("Scan " + scan.getID() + " is on a different object. Dropping from dataset.");
                    scans.remove(i);
                }
            }
        }


        super.validate(scans);		
    }


    @Override
    public int maxPixels() {
        return maxFeeds;
    }

    @Override
    public SourceModel getSourceModelInstance(List<Scan<?>> scans) {
        if(hasOption("chopped")) return new ChopNodPhotometry(this);
        return super.getSourceModelInstance(scans);
    }


    @Override
    public String getDataLocationHelp() {
        return super.getDataLocationHelp() +
                "                    'datapath' can be either the directory containing the\n" +
                "                    scans (FITS files or scan folders) themselves, or the\n" +
                "                    location in which project sub-directories reside.\n" +
                "     -project=      The project ID (case insensitive). E.g. 'T-79.F-0002-2007'.\n";
    }

}
