package uk.ac.starlink.oldfits;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.TransformerException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.BufferedDataOutputStream;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import uk.ac.starlink.array.AccessMode;
import uk.ac.starlink.array.ArrayAccess;
import uk.ac.starlink.array.BadHandler;
import uk.ac.starlink.array.NDArray;
import uk.ac.starlink.array.NDArrays;
import uk.ac.starlink.array.OrderedNDShape;
import uk.ac.starlink.array.ScratchNDArray;
import uk.ac.starlink.array.Type;
import uk.ac.starlink.hdx.HdxContainer;
import uk.ac.starlink.hdx.HdxFactory;
import uk.ac.starlink.hdx.HdxResourceType;
import uk.ac.starlink.ndx.DefaultMutableNdx;
import uk.ac.starlink.ndx.MutableNdx;
import uk.ac.starlink.ndx.Ndx;
import uk.ac.starlink.ndx.NdxHandler;
import uk.ac.starlink.ndx.NdxIO;
import uk.ac.starlink.util.SourceReader;
import uk.ac.starlink.util.TestCase;

public class FitsNdxTest extends TestCase {

    private String tmpdir;

    public FitsNdxTest( String name ) {
        super( name );
        Logger.getLogger( "uk.ac.starlink.ndx" ).setLevel( Level.WARNING );
        Logger.getLogger( "uk.ac.starlink.oldfits" ).setLevel( Level.WARNING );
    }

    public void setUp() {
        tmpdir = System.getProperty( "java.io.tmpdir" );
    }

    public void testHandler() throws TransformerException, IOException,
                                     MalformedURLException {
        NdxHandler handler = FitsNdxHandler.getInstance();

        /* Get an NdxIO and check we are installed there. */
        NdxIO ndxio = new NdxIO();
        List handlers = ndxio.getHandlers();
        boolean hasFitsHandler = false;
        for ( Iterator it = handlers.iterator(); it.hasNext(); ) {
            if ( it.next() instanceof FitsNdxHandler ) {
                hasFitsHandler = true;
            }
        }
        assertTrue( hasFitsHandler );

        /* Write the NDX out as XML. */
        Ndx ndx = virtualNdx();
        String xloc = tmpdir + File.separator + "copy.xml";
        ndxio.outputNdx( xloc, ndx );
        Ndx xndx = ndxio.makeNdx( xloc, AccessMode.READ );
        assertNdxEqual( ndx, xndx );

        /* Write the NDX out as a new FITS file via the NdxIO. */
        ndxio.setHandlers( new NdxHandler[] { FitsNdxHandler.getInstance() } );
        String hloc = tmpdir + File.separator + "copy.fits";
        ndxio.outputNdx( hloc, ndx );
        Ndx hndx = ndxio.makeNdx( hloc, AccessMode.READ );
        assertNdxEqual( ndx, hndx );

        /* Write it out directly. */
        File ofile = new File( tmpdir, "copy2.fits" );
        ArrayDataOutput ostrm =
            new BufferedDataOutputStream( new FileOutputStream( ofile ) );
        FitsNdxHandler.getInstance()
                      .outputNdx( ostrm, ofile.toURI().toURL(), xndx );
        ostrm.close();
        Ndx fndx = ndxio.makeNdx( ofile.toURI().toURL(), AccessMode.READ );
        assertNdxEqual( ndx, fndx );
    }

    public void testDOM()
            throws TransformerException, IOException,
            uk.ac.starlink.hdx.HdxException,
            java.net.MalformedURLException,
            javax.xml.parsers.ParserConfigurationException,
            org.xml.sax.SAXException {
        /*
         * Exercise the HdxFacade and HdxDocumentFactory
         * implementations of FitsNdxHandler
         */

        /*
         * Again using the virtualNdx generated below, write out the
         * NDX as XML and a FITS file (as above)
         */
        NdxIO ndxio = new NdxIO();

        /*
         * This NDX will be round-tripped back to Ndx via FITS, and
         * via XML for comparison
         */
        Ndx ndx = virtualNdx();
        String xloc = tmpdir + File.separator + "copy.xml";
        ndxio.outputNdx(xloc, ndx);

        // Write the NDX out as a new FITS file.
        ndxio.setHandlers(new NdxHandler[] { FitsNdxHandler.getInstance() } );
        String hloc = tmpdir + File.separator + "copy.fits";
        ndxio.outputNdx(hloc, ndx);

        // Setup done

        /*
         * Check whether the HdxDocumentFactory implementation
         * produces XML which is compatible with the XML generated by
         * outputNdx.  Read the FITS file as an HDX...
         */
        HdxFactory hdxfact = HdxFactory.getInstance();
        HdxContainer hdx = hdxfact.newHdxContainer
                ( new java.net.URL( "file:" + hloc ) );

        // ...now read the XML file
        org.w3c.dom.Document doc =
                javax.xml.parsers.DocumentBuilderFactory
                .newInstance()
                .newDocumentBuilder()
                .parse( xloc );

        /*
         * The comparison here is slightly subtle.  We can't just
         * compare the DOMs directly, because the URLs
         * pointing to the various FITS components won't be the same
         * -- the data for copy.xml is in a separate FITS file called
         * copy-data.fits, and the data for copy.fits is in the same
         * FITS file; so ignore the attribute values.  The DOM
         * obtained from xloc will have extra insignificant whitespace
         * in it, so ignore that, too.
         */
        assertDOMEquals( doc.getDocumentElement(), hdx.getDOM( null ),
                         null,
                         TestCase.IGNORE_ATTRIBUTE_VALUE | TestCase.IGNORE_WHITESPACE);

        /*
         * Check HdxFacade by extracting elements from the DOM, then
         * extracting the corresponding objects
         */
        Element dom = hdx.getDOM( null );

        HdxResourceType hdxt = HdxResourceType.match( "image" );
        assertNotNull( hdxt );
        assertTrue( hdxt != HdxResourceType.NONE );
        Object obj = hdx.get( hdxt );
        assertNotNull( obj );
        assertTrue( hdxt.getConstructedClass().isInstance( obj ) );
        NDArray arr = ndx.getImage();
        assertNotNull( arr );
        assertTrue( NDArrays.equals( arr, (NDArray)obj ) );
        assertTrue(NDArrays.equals
                   ( arr, extractArray( hdxfact, dom, hdxt, "image" ) ) );

        hdxt = HdxResourceType.match( "variance" );
        assertNotNull( hdxt );
        assertTrue( hdxt != HdxResourceType.NONE );
        obj = hdx.get( hdxt );
        assertNotNull( obj );
        assertTrue( hdxt.getConstructedClass().isInstance( obj ) );
        arr = ndx.getVariance();
        assertNotNull( arr );
        assertTrue( NDArrays.equals(arr, (NDArray)obj) );
        assertTrue(NDArrays.equals
                   ( arr, extractArray( hdxfact, dom, hdxt, "variance" ) ) );

        hdxt = HdxResourceType.match( "quality" );
        assertNotNull( hdxt );
        assertTrue( hdxt != HdxResourceType.NONE );
        obj = hdx.get( hdxt );
        assertNotNull( obj );
        assertTrue( hdxt.getConstructedClass().isInstance( obj ) );
        arr = ndx.getQuality();
        assertTrue( NDArrays.equals(arr, (NDArray)obj) );
        assertTrue(NDArrays.equals
                   ( arr, extractArray( hdxfact, dom, hdxt, "quality" ) ) );

    }
    
    private void assertNdxEqual( Ndx ndx1, Ndx ndx2 )
            throws IOException, TransformerException {
        assertTrue( NDArrays.equals( ndx1.getImage(), ndx2.getImage() ) );
        assertEquals( ndx1.hasVariance(), ndx2.hasVariance() );
        assertEquals( ndx1.hasQuality(), ndx2.hasQuality() );
        assertEquals( ndx1.hasTitle(), ndx2.hasTitle() );

        assertEquals( "Etc status", ndx1.hasEtc(), ndx2.hasEtc() );

        if ( ndx1.hasVariance() ) {
            assertTrue( NDArrays.equals( ndx1.getVariance(),
                                         ndx2.getVariance() ) );
        }
        if ( ndx1.hasQuality() ) {
            assertTrue( NDArrays.equals( ndx1.getQuality(),
                                         ndx2.getQuality() ) );
        }
        if ( ndx1.hasTitle() ) {
            assertEquals( ndx1.getTitle(), ndx2.getTitle() );
        }
        if ( ndx1.hasEtc() ) {
            StringWriter sw1 = new StringWriter();
            StringWriter sw2 = new StringWriter();
            SourceReader sr = new SourceReader();
            sr.setIncludeDeclaration( false );
            sr.setIndent( 0 );
            sr.writeSource( ndx1.getEtc(), sw1 );
            sr.writeSource( ndx2.getEtc(), sw2 );
            assertEquals( sw1.toString().replaceAll( "\\s", "" ),
                          sw2.toString().replaceAll( "\\s", "" ) );
        }
    }

    private Ndx virtualNdx() throws TransformerException, IOException {

        OrderedNDShape shape = new OrderedNDShape( new long[] { 1, 11, 21 },
                                                   new long[] { 10, 20, 30 },
                                                   null );
        Type type = Type.FLOAT;
        BadHandler bh = type.defaultBadHandler();
        NDArray im = new ScratchNDArray( shape, type, bh );
        NDArray var = new ScratchNDArray( shape, type, bh );
        BadHandler nbh = BadHandler.getHandler( Type.BYTE, null );
        NDArray qual = new ScratchNDArray( shape, Type.BYTE, nbh );
        int npix = (int) shape.getNumPixels();
        ArrayAccess iacc = im.getAccess();
        ArrayAccess vacc = var.getAccess();
        ArrayAccess qacc = qual.getAccess();
        assertTrue( iacc.isMapped() );
        assertTrue( vacc.isMapped() );
        assertTrue( qacc.isMapped() );
        float[] imap = (float[]) iacc.getMapped();
        float[] vmap = (float[]) vacc.getMapped();
        byte[] qmap = (byte[]) qacc.getMapped();
        byte qval = 0;
        fillCycle( imap, 0, 100 );
        for ( int i = 0; i < npix; i++ ) {
            vmap[ i ] = imap[ i ] * imap[ i ];
            qmap[ i ] = qval++;
        }
        iacc.close();
        vacc.close();
        qacc.close();
        MutableNdx ndx = new DefaultMutableNdx( im );
        ndx.setVariance( var );
        ndx.setQuality( qual );
        ndx.setTitle( "NDX for FITS testing" );
        ndx.setBadBits( 128 );
        String etcstr = 
              "<etc>"
            + "<weather><cloudy/><temperature value='cold'/></weather>"
            + "<location><altitude value='quite high'/></location>"
            + "</etc>";
        Node etc = new SourceReader()
                  .getDOM( new StreamSource( new StringReader( etcstr ) ) );
        ndx.setEtc( etc );
        return ndx;
    }

    /**
     * Checks element/object translations.  Finds the `component'
     * child of the given element, obtains the corresponding object
     * from the <em>factory</em>, as opposed to from the Hdx object,
     * checks that the returned object is the correct type, and
     * returns the array.
     */
    private NDArray extractArray( HdxFactory fact,
                                  Element el,
                                  HdxResourceType type,
                                  String component )
            throws uk.ac.starlink.hdx.HdxException {
        NodeList nl = el.getElementsByTagName( component );
        assertEquals( 1, nl.getLength() );
        Object obj = fact.getObject( (Element)nl.item( 0 ) );
        assertNotNull( obj );
        assertTrue( type.getConstructedClass().isInstance(obj) );
        // return the round-tripped image component
        return (NDArray)obj;
    }
}
