/*******************************************************************************
 * 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;

import crush.Frame;
import crush.Integration;
import crush.Signal;
import crush.motion.Motion;
import jnum.astro.AstroProjector;
import jnum.astro.EquatorialCoordinates;
import jnum.math.SphericalCoordinates;
import jnum.math.Vector2D;
import jnum.projection.Projector2D;

public abstract class TelescopeFrame extends Frame {
    /**
     * 
     */
    private static final long serialVersionUID = -3917292618625828297L;
   
    
    public boolean hasTelescopeInfo = true;
    
    public EquatorialCoordinates equatorial;
    public Vector2D chopperPosition = new Vector2D(); // in the native coordinate system, standard direction (e.g. -RAO, DECO)
    
    public double LST;

    private float transmission = 1.0F;

       
    protected TelescopeFrame(Integration<? extends TelescopeFrame> parent) {
        super(parent);
    }
    
    @Override
    public TelescopeFrame copy(boolean withContents) {
        TelescopeFrame copy = (TelescopeFrame) super.copy(withContents);
        
        if(equatorial != null) copy.equatorial = equatorial.copy();
        if(chopperPosition != null) copy.chopperPosition = chopperPosition.copy();

        return copy;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public TelescopeScan<? extends Integration<? extends TelescopeFrame>> getScan() { 
        return (TelescopeScan<? extends Integration<? extends TelescopeFrame>>) super.getScan(); 
    }
    
    @Override
    public TelescopeInstrument<?> getInstrument() { return (TelescopeInstrument<?>) getIntegration().getInstrument(); }
  
    public float getTransmission() { return transmission; }
    
    protected void setTransmission(float value) { transmission = value; }
    
    protected void setTransmission(double value) { setTransmission((float) value); }
    
    public float getTransmissionCorrection(Signal atm, float signal2emissivity) {
        return atm.valueAt(this) * signal2emissivity;
    }
    
    @Override
    public float getSourceGain(final int mode) throws IllegalArgumentException {
        if(mode == TOTAL_POWER) return sign * getTransmission();
        throw new IllegalArgumentException(getClass().getSimpleName() + " does not define signal mode " + mode);
    }
    
    
    @Override
    public boolean validate() {
        if(!super.validate()) return false;
        
        if(!hasTelescopeInfo) return true;
        
        // Set the platform rotation, unless the rotation was explicitly set already
        if(Double.isNaN(getRotation())) {
            switch(getInstrument().mount) {
            case CASSEGRAIN: 
            case GREGORIAN: 
            case NASMYTH_COROTATING:
            case PRIME_FOCUS: setRotation(0.0); break;
            case LEFT_NASMYTH: setRotation(-getNativeCoords().y()); break;
            case RIGHT_NASMYTH: setRotation(getNativeCoords().y()); break;
            default: setRotation(0.0);
            }       
        }
        
        return true;
    }
    
    
    public void getEquatorial(final Vector2D position, final EquatorialCoordinates coords) {
        coords.setNativeLongitude(equatorial.x() + getNativeX(position) / getScan().equatorial.cosLat());
        coords.setNativeLatitude(equatorial.y() + getNativeY(position));
    }
    
    public void getEquatorialNativeOffset(final Vector2D position, final Vector2D offset) {
        getEquatorialNativeOffset(offset);
        offset.setX(offset.x() + getNativeX(position));
        offset.setY(offset.y() + getNativeY(position));
    }
    
    @Override
    public SphericalCoordinates getNativeCoords() { return equatorial; }
        
    public void getNativeOffset(final Vector2D position, final Vector2D offset) {
        getEquatorialNativeOffset(position, offset);
    }
    
    @Override
    public void getNativeOffset(final Vector2D offset) {
        getEquatorialNativeOffset(offset);
    }
    

    
    public void getEquatorialNativeOffset(Vector2D offset) {
        equatorial.getNativeOffsetFrom(getScan().equatorial, offset);        
    }
    
    public Vector2D getEquatorialNativeOffset() {
        Vector2D offset = new Vector2D();
        getEquatorialNativeOffset(offset);
        return offset;
    }
    
    public void getApparentEquatorial(EquatorialCoordinates apparent) {
        apparent.copy(equatorial);
        getScan().toApparent.precess(apparent);
    }
    

    
    @Override
    public void pointingAt(Vector2D offset) {
        Vector2D nativeOffset = getNativeOffset();
        if(nativeOffset != null) nativeOffset.subtract(offset);
        SphericalCoordinates coords = getNativeCoords();
        if(coords != null) coords.subtractOffset(offset);
    }

    
    @Override
    public void project(final Vector2D fpOffset, final Projector2D<?> projector) {
        if(projector instanceof AstroProjector) project(fpOffset, (AstroProjector) projector);
    }
    
    private void project(final Vector2D fpOffset, final AstroProjector projector) {
        if(projector.isFocalPlane()) {
            projector.setReferenceCoords();
            // Deproject SFL focal plane offsets...
            final Vector2D offset = projector.getOffset();
            getFocalPlaneOffset(fpOffset, offset);
            projector.getCoordinates().addNativeOffset(offset);
            projector.reproject();
        }
        else if(getScan().isNonSidereal) {
            projector.setReferenceCoords();
            // Deproject SFL native offsets...
            final EquatorialCoordinates eq = projector.getEquatorial();
            Vector2D offset = projector.getOffset();
            getEquatorialNativeOffset(fpOffset, offset);
            eq.addNativeOffset(offset);
            projector.setEquatorial(eq);
        }
        else {
            final EquatorialCoordinates eq = projector.getEquatorial();
            getEquatorial(fpOffset, eq);     
            projector.setEquatorial(eq);
        }
    
    }   
    

    // Native offsets are in standard directions (e.g. -RA, DEC)
    public void nativeToNativeEquatorial(Vector2D offset) {}
    
    public final void nativeToEquatorial(Vector2D offset) {
        nativeToNativeEquatorial(offset);
        offset.scaleX(-1.0);
    }
    
    // Native offsets are in standard directions (e.g. -RA, DEC)
    public void nativeEquatorialToNative(Vector2D offset) {}
    
    public final void equatorialToNative(Vector2D offset) {
        offset.scaleX(-1.0);
        nativeEquatorialToNative(offset);
    }
    
    public void nativeToEquatorial(SphericalCoordinates coords, EquatorialCoordinates equatorial) {
        equatorial.copy(coords);
    }
    
    public void equatorialToNative(EquatorialCoordinates equatorial, SphericalCoordinates coords) {
        coords.copy(equatorial);
    }
    
    
    @Override
    public Vector2D getPosition(final int type) {
        Vector2D pos = new Vector2D();
        
        // Telescope motion should be w/o chopper...
        // TELESCOPE motion with or w/o SCANNING and CHOPPER
        if((type & Motion.TELESCOPE) != 0) {
            SphericalCoordinates coords = new SphericalCoordinates();
            
            coords.copy(getNativeCoords());
            // Subtract the chopper motion if it is not requested...
            if((type & Motion.CHOPPER) == 0) coords.subtractNativeOffset(chopperPosition);
            pos.copy(coords);

            if((type & Motion.PROJECT_GLS) != 0) pos.scaleX(coords.cosLat());
        }

        // Scanning includes the chopper motion
        // SCANNING with or without CHOPPER
        else if((type & Motion.SCANNING) != 0) {
            getNativeOffset(pos);
            // Subtract the chopper motion if it is not requested...
            if((type & Motion.CHOPPER) == 0) pos.subtract(chopperPosition);
        }   

        // CHOPPER only...
        else if(type == Motion.CHOPPER) pos.copy(chopperPosition);
        
        return pos;
    }
    
    
    public static int CHOP_LEFT = frameFlags.next('L', "Chop Left").value();
    public static int CHOP_RIGHT = frameFlags.next('R', "Chop Right").value();
    public static int CHOP_TRANSIT = frameFlags.next('T', "Chop Transit").value();
    public static int CHOP_FLAGS = CHOP_LEFT | CHOP_RIGHT | CHOP_TRANSIT;
    
    public static int NOD_LEFT = frameFlags.next('<', "Nod Left").value();
    public static int NOD_RIGHT = frameFlags.next('>', "Nod Right").value();

}
