package uk.ac.starlink.topcat.plot2;

import uk.ac.starlink.ttools.plot2.Scale;
import uk.ac.starlink.ttools.plot2.SurfaceFactory;
import uk.ac.starlink.ttools.plot2.config.ConfigKey;

/**
 * Abstract superclass for simple Cartesian plots.
 * This mostly just handles the axis labelling GUI, and leaves subclasses
 * to adjust the details of the other configuration options.
 *
 * @author   Mark Taylor
 * @since    14 Mar 2013
 */
public abstract class CartesianAxisController<P,A> extends AxisController<P,A> {

    private final int ndim_;
    private final ConfigKey<String>[] axisLabelKeys_;
    private final String[] axisDefaultNames_;
    private final AutoConfigSpecifier labelSpecifier_;

    /**
     * Constructor.
     *
     * @param  surfFact  plot surface factory
     * @param  axisLabelKeys  config keys for axis labels
     */
    @SuppressWarnings("this-escape")
    public CartesianAxisController( SurfaceFactory<P,A> surfFact,
                                    ConfigKey<String>[] axisLabelKeys ) {
        super( surfFact );
        ndim_ = axisLabelKeys.length;
        axisLabelKeys_ = axisLabelKeys;

        /* Set up a specifier component to get axis label values.
         * Each specifier has an automatic default. */
        labelSpecifier_ = new AutoConfigSpecifier( axisLabelKeys );
        axisDefaultNames_ = new String[ ndim_ ];
        for ( int id = 0; id < ndim_; id++ ) {
            axisDefaultNames_[ id ] = axisLabelKeys[ id ].getDefaultValue();
        }
        configureForLayers( new LayerControl[ 0 ] );
    }

    public void configureForLayers( LayerControl[] controls ) {

        /* Fix it so that the default values for the axis labels are
         * taken from coordinate labels (table column names or expressions)
         * for data layers contributing to the plot.  These have to be
         * updated when the content of the plot control stack changes. */
        LayerControl leadControl = controls.length > 0 ? controls[ 0 ] : null;
        String[] axLabels = getAxisLabels( leadControl );
        for ( int id = 0; id < ndim_; id++ ) {
            labelSpecifier_.getAutoSpecifier( axisLabelKeys_[ id ] )
                           .setAutoValue( axLabels[ id ] );
        }
    }

    /**
     * Returns suitable axis labels for a plot generated by the given layer
     * control.
     *
     * @param   control  layer control, may be null
     * @return  ndim-element array of axis labels that can be used if
     *          no user-specified values are available
     */
    private String[] getAxisLabels( LayerControl control ) {
        if ( control == null ) {
            return axisDefaultNames_;
        }
        else {
            String[] labels = new String[ ndim_ ];
            for ( int id = 0; id < ndim_; id++ ) {
                String label = control.getCoordLabel( axisDefaultNames_[ id ] );
                labels[ id ] = label == null ? axisDefaultNames_[ id ] : label;
            }
            return labels;
        }
    }

    /**
     * Adds the axis label configuration tab set up by this component.
     * It's not done in the constructor so that subclasses can decide
     * where it goes in terms of the other config tabs.
     */
    protected void addLabelsTab() {
        getMainControl().addSpecifierTab( "Labels", labelSpecifier_ );
    }

    /**
     * Returns the specifier used for axis labels.
     *
     * @return  axis label specifier
     */
    public AutoConfigSpecifier getLabelSpecifier() {
        return labelSpecifier_;
    }

    @Override
    protected boolean forceClearRange( P oldProfile, P newProfile ) {

        /* If log scaling of some axes has changed, we unconditionally
         * re-range.  This might not always be the best thing to do
         * (could be improved), but as it stands if not we run the risk
         * of trying to plot negative logarithmic values, which will
         * cause an exception in the plotting. */
        return logChanged( oldProfile, newProfile );
    }

    /**
     * Indicates whether the scaling has changed to or from logarithmic
     * for any of the cartesian axes between two profiles.
     * If so, it's going to be necessary to rescale, since attempting
     * a log plot with negative values would fail.
     *
     * @param  prof1  first profile
     * @param  prof2  second profile
     * @return  true iff some of the axes are log in prof1 and linear in prof2
     *               or vice versa
     */
    protected abstract boolean logChanged( P prof1, P prof2 );

    /**
     * Determines whether the logarithmic (actually: positive definite)
     * status has changed for any of the elements of two matched Scale arrays.
     *
     * @param  scales1  first array of axis scales
     * @param  scales2  second array of axis scales, same size as first
     * @return  true iff the logarithmic status has changed between
     *          first and second array of any of the elements
     */
    public static boolean logChanged( Scale[] scales1, Scale[] scales2 ) {
        assert scales1.length == scales2.length;
        for ( int i = 0; i < scales1.length; i++ ) {
            if ( scales1[ i ].isPositiveDefinite() !=
                 scales2[ i ].isPositiveDefinite() ) {
                return true;
            }
        }
        return false;
    }
}
