/*******************************************************************************
 * Copyright (c) 2019 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;

import crush.Channel;
import crush.Instrument;
import crush.Pixel;
import crush.PixelLayout;
import jnum.Unit;
import jnum.math.Vector2D;
import nom.tam.fits.BinaryTableHDU;
import nom.tam.fits.FitsException;
import nom.tam.fits.Header;

public class APEXLayout extends PixelLayout {
    /**
     * 
     */
    private static final long serialVersionUID = -8237785228686714439L;
    
    private Vector2D referencePosition;
    private int referencePixelFixedIndex;
    
    private Hashtable<Integer, String> feedTypes;
    
    protected APEXLayout(APEXInstrument<? extends Channel> instrument) {
        super(instrument);
    }

    @Override
    public APEXLayout copyFor(Instrument<? extends Channel> instrument) {
        APEXLayout copy = (APEXLayout) super.copyFor(instrument);
        copy.referencePosition = referencePosition.copy();
        return copy;
    }
    
    public int getReferencePixelFixedIndex() { return referencePixelFixedIndex; }
    
    public void parseHDU(BinaryTableHDU hdu) throws FitsException {
        Header header = hdu.getHeader();

        // Parse the feed type descriptions...
        feedTypes = new Hashtable<>();
        StringTokenizer typeCodes = new StringTokenizer(header.getStringValue("FDTYPCOD"), ",");
        while(typeCodes.hasMoreTokens()) {
            StringTokenizer assignment = new StringTokenizer(typeCodes.nextToken(), ":");
            feedTypes.put(Integer.parseInt(assignment.nextToken()), assignment.nextToken().trim());
        }      
        
        Object[] data = hdu.getRow(0);

        int[] beSection = (int[]) data[hdu.findColumn("BESECTS")];
        int[] feedType = (int[]) data[hdu.findColumn("FEEDTYPE")];
        
        double[] xOffset = (double[]) data[hdu.findColumn("FEEDOFFX")];
        double[] yOffset = (double[]) data[hdu.findColumn("FEEDOFFY")];

        float[][] beamEff = (float[][]) data[hdu.findColumn("BEAMEFF")];    // [ch][pix]
        float[][] forwardEff = (float[][]) data[hdu.findColumn("ETAFSS")];  // [ch][pix]
        //float[][] gain = (float[][]) data[hdu.findColumn("FLATFIEL")];
        
        // TODO gain-elevation correction...
        
        int np = beSection.length;
        
        ArrayList<Pixel> pixels = getPixels();
        pixels.clear();
        pixels.ensureCapacity(np);
        
        APEXInstrument<? extends Channel> instrument = getInstrument();
 
        for(int p=0; p < np; p++) {
            final APEXPixel pixel = new APEXPixel(instrument, Integer.toString(p+1), p+1);
            pixel.fitsPosition = new Vector2D(xOffset[p] * Unit.deg, yOffset[p] * Unit.deg);
            pixel.setPosition(pixel.fitsPosition.copy());
            pixel.coupling = beamEff[0][p] / forwardEff[0][p]; // TODO channel-wise...
            pixel.type = feedType[p];
            pixel.beIndex = beSection[p] - 1;
            pixels.add(pixel);
        }

        referencePixelFixedIndex = ((int[]) data[hdu.findColumn("REFFEED")])[0];
        instrument.info(np + " pixels found. Reference pixel is " + referencePixelFixedIndex);

        APEXPixel ref = (APEXPixel) pixels.get(referencePixelFixedIndex - 1);
        referencePosition = ref.fitsPosition;

        // DEWRTMOD: system CABIN, EQUA, or HORIZ
        
        double rotation = header.getDoubleValue("DEWUSER", 0.0) - header.getDoubleValue("DEWZERO", 0.0);
        if(rotation != 0.0) {
            instrument.info("Dewar rotated at " + rotation + " deg.");
            rotation *= Unit.deg;
        }
        instrument.setRotationAngle(rotation);

        // Take instrument rotation into account
        for(Pixel pixel : pixels) pixel.getPosition().rotate(rotation);
    }
 
    

    public void assignChannels() {
        int npc = getInstrument().pixelChannels;
        
        for(Pixel p : getPixels()) {
            APEXPixel pixel = (APEXPixel) p;
            pixel.clear();
            int startChannel = pixel.beIndex * npc;
            for(int c=0; c < npc; c++) pixel.add(getInstrument().get(startChannel + c));
        }
    }
    
    @Override
    public APEXInstrument<? extends Channel> getInstrument() { return (APEXInstrument<? extends Channel>) super.getInstrument(); }

    @Override
    public Pixel getPixelInstance(int fixedIndex, String id) { return new APEXPixel(getInstrument(), id, fixedIndex); }


    @Override
    public void setDefaultPixelPositions() {
        for(Pixel p : getPixels()) p.setPosition(((APEXPixel) p).fitsPosition.copy());
    }
    
    @Override
    public void readRCP(String fileName)  throws IOException {      
        super.readRCP(fileName);
        recenter(); 
    }
    
    
    public ArrayList<APEXPixel> getNeighbours(APEXPixel pixel, double radius) {
        ArrayList<APEXPixel> neighbours = new ArrayList<>();
        for(Pixel p2 : getMappingPixels(0)) if(p2 != pixel) if(p2.distanceTo(pixel) <= radius) neighbours.add((APEXPixel) p2);
        return neighbours;      
    }

    public void recenter() {
        setReferencePosition(referencePosition);
    }
    
    @Override
    public Object getTableEntry(String name) {
        if(name.equals("ref")) return referencePixelFixedIndex;
        return super.getTableEntry(name);
    }
    
}
