package nom.tam.fits;

import nom.tam.util.ArrayFuncs;

/*
 * This code is part of the Java FITS library developed 1996-2012 by T.A. McGlynn (NASA/GSFC)
 * The code is available in the public domain and may be copied, modified and used
 * by anyone in any fashion for any purpose without restriction. 
 * 
 * No warranty regarding correctness or performance of this code is given or implied.
 * Users may contact the author if they have questions or concerns.
 * 
 * The author would like to thank many who have contributed suggestions, 
 * enhancements and bug fixes including:
 * David Glowacki, R.J. Mathar, Laurent Michel, Guillaume Belanger,
 * Laurent Bourges, Rose Early, Fred Romelfanger, Jorgo Baker, A. Kovacs, V. Forchi, J.C. Segovia,
 * Booth Hartley and Jason Weiss.  
 * I apologize to any contributors whose names may have been inadvertently omitted.
 * 
 *      Tom McGlynn
 */

/** Random groups HDUs.  Note that the internal storage of random
 * groups is a Object[ngroup][2] array.  The first element of
 * each group is the parameter data from that group.  The second element
 * is the data.  The parameters should be a one dimensional array
 * of the primitive types byte, short, int, long, float or double.
 * The second element is a n-dimensional array of the same type.
 * When analyzing group data structure only the first group is examined,
 * but for a valid FITS file all groups must have the same structure.
 */
public class RandomGroupsHDU extends BasicHDU {

    Object dataArray;

    /** Create an HDU from the given header and data */
    public RandomGroupsHDU(Header h, Data d) {
        myHeader = h;
        myData = d;
    }

    /** Indicate that a RandomGroupsHDU can come at
     *  the beginning of a FITS file.
     */
    protected boolean canBePrimary() {
        return true;
    }

    /** Move a RandomGroupsHDU to or from the beginning
     *  of a FITS file.  Note that the FITS standard only
     *  supports Random Groups data at the beginning
     *  of the file, but we allow it within Image extensions.
     */
    protected void setPrimaryHDU(boolean status) {
        try {
            super.setPrimaryHDU(status);
        } catch (FitsException e) {
            System.err.println("Unreachable catch in RandomGroupsHDU");
        }
        if (status) {
            myHeader.setSimple(true);
        } else {
            myHeader.setXtension("IMAGE");
        }
    }

    /** Make a header point to the given object.
     * @param odata The random groups data the header should describe.
     */
    static Header manufactureHeader(Data d) throws FitsException {

        if (d == null) {
            throw new FitsException("Attempt to create null Random Groups data");
        }
        Header h = new Header();
        d.fillHeader(h);
        return h;

    }

    /** Is this a random groups header?
     * @param hdr The header to be tested.
     */
    public static boolean isHeader(Header hdr) {

        if (hdr.getBooleanValue("SIMPLE")) {
            return hdr.getBooleanValue("GROUPS");
        }

        String s = hdr.getStringValue("XTENSION");
        if (s.trim().equals("IMAGE")) {
            return hdr.getBooleanValue("GROUPS");
        }

        return false;
    }

    /** Check that this HDU has a valid header.
     * @return <CODE>true</CODE> if this HDU has a valid header.
     */
    public boolean isHeader() {
        return isHeader(myHeader);
    }

    /** Check if this data is compatible with Random Groups structure.
     *  Must be an Object[ngr][2] structure with both elements of each
     *  group having the same base type and the first element being
     *  a simple primitive array.  We do not check anything but
     *  the first row.
     */
    public static boolean isData(Object oo) {
        if (oo instanceof Object[][]) {

            Object[][] o = (Object[][]) oo;

            if (o.length > 0) {
                if (o[0].length == 2) {
                    if (ArrayFuncs.getBaseClass(o[0][0])
                            == ArrayFuncs.getBaseClass(o[0][1])) {
                        String cn = o[0][0].getClass().getName();
                        if (cn.length() == 2 && cn.charAt(1) != 'Z'
                                || cn.charAt(1) != 'C') {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /** Create a FITS Data object corresponding to
     * this HDU header.
     */
    public Data manufactureData() throws FitsException {
        return manufactureData(myHeader);
    }

    /** Create FITS data object corresponding to a given header.
     */
    public static Data manufactureData(Header hdr) throws FitsException {

        int gcount = hdr.getIntValue("GCOUNT", -1);
        int pcount = hdr.getIntValue("PCOUNT", -1);

        if (!hdr.getBooleanValue("GROUPS")
                || hdr.getIntValue("NAXIS1", -1) != 0
                || gcount < 0 || pcount < 0
                || hdr.getIntValue("NAXIS") < 2) {
            throw new FitsException("Invalid Random Groups Parameters");
        }

        // Allocate the object.
        Object[][] dataArray;

        if (gcount > 0) {
            dataArray = new Object[gcount][2];
        } else {
            dataArray = new Object[0][];
        }

        Object[] sampleRow = generateSampleRow(hdr);
        for (int i = 0; i < gcount; i += 1) {
            ((Object[][]) dataArray)[i][0] =
                    ((Object[]) nom.tam.util.ArrayFuncs.deepClone(sampleRow))[0];
            ((Object[][]) dataArray)[i][1] =
                    ((Object[]) nom.tam.util.ArrayFuncs.deepClone(sampleRow))[1];
        }
        return new RandomGroupsData(dataArray);

    }

    static Object[] generateSampleRow(Header h)
            throws FitsException {

        int ndim = h.getIntValue("NAXIS", 0) - 1;
        int[] dims = new int[ndim];

        int bitpix = h.getIntValue("BITPIX", 0);


        Class baseClass;

        switch (bitpix) {
            case 8:
                baseClass = Byte.TYPE;
                break;
            case 16:
                baseClass = Short.TYPE;
                break;
            case 32:
                baseClass = Integer.TYPE;
                break;
            case 64:
                baseClass = Long.TYPE;
                break;
            case -32:
                baseClass = Float.TYPE;
                break;
            case -64:
                baseClass = Double.TYPE;
                break;
            default:
                throw new FitsException("Invalid BITPIX:" + bitpix);
        }

        // Note that we have to invert the order of the axes
        // for the FITS file to get the order in the array we
        // are generating.  Also recall that NAXIS1=0, so that
        // we have an 'extra' dimension.

        for (int i = 0; i < ndim; i += 1) {
            long cdim = h.getIntValue("NAXIS" + (i + 2), 0);
            if (cdim < 0) {
                throw new FitsException("Invalid array dimension:" + cdim);
            }
            dims[ndim - i - 1] = (int) cdim;
        }

        Object[] sample = new Object[2];
        sample[0] = ArrayFuncs.newInstance(baseClass, h.getIntValue("PCOUNT"));
        sample[1] = ArrayFuncs.newInstance(baseClass, dims);

        return sample;
    }

    public static Data encapsulate(Object o) throws FitsException {
        if (o instanceof Object[][]) {
            return new RandomGroupsData((Object[][]) o);
        } else {
            throw new FitsException("Attempt to encapsulate invalid data in Random Group");
        }
    }

    /** Display structural information about the current HDU.
     */
    public void info() {

        System.out.println("Random Groups HDU");
        if (myHeader != null) {
            System.out.println("   HeaderInformation:");
            System.out.println("     Ngroups:" + myHeader.getIntValue("GCOUNT"));
            System.out.println("     Npar:   " + myHeader.getIntValue("PCOUNT"));
            System.out.println("     BITPIX: " + myHeader.getIntValue("BITPIX"));
            System.out.println("     NAXIS:  " + myHeader.getIntValue("NAXIS"));
            for (int i = 0; i < myHeader.getIntValue("NAXIS"); i += 1) {
                System.out.println("      NAXIS" + (i + 1) + "= "
                        + myHeader.getIntValue("NAXIS" + (i + 1)));
            }
        } else {
            System.out.println("    No Header Information");
        }


        Object[][] data = null;
        if (myData != null) {
            try {
                data = (Object[][]) myData.getData();
            } catch (FitsException e) {
                data = null;
            }
        }

        if (data == null || data.length < 1 || data[0].length != 2) {
            System.out.println("    Invalid/unreadable data");
        } else {
            System.out.println("    Number of groups:" + data.length);
            System.out.println("    Parameters: " + nom.tam.util.ArrayFuncs.arrayDescription(data[0][0]));
            System.out.println("    Data:" + nom.tam.util.ArrayFuncs.arrayDescription(data[0][1]));
        }
    }
}
