########################################################################3
#  imcontsub_test.py
#
#
# Copyright (C) 2008, 2009
# Associated Universities, Inc. Washington DC, USA.
#
# This scripts free software; you can redistribute it and/or modify it
# under the terms of the GNU Library General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This library 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 Library General Public
# License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
#
# Correspondence concerning AIPS++ should be adressed as follows:
#        Internet email: aips2-request@nrao.edu.
#        Postal address: AIPS++ Project Office
#                        National Radio Astronomy Observatory
#                        520 Edgemont Road
#                        Charlottesville, VA 22903-2475 USA
#
# <author>
# Shannon Jaeger (University of Calgary)
# </author>
#
# <summary>
# Test suite for the CASA imcontsub Task
# </summary>
#
# <reviewed reviwer="" date="" tests="" demos="">
# </reviewed
#
# <etymology>
# imcontsub_test stands for image momemnts test
# </etymology>
#
# <synopsis>
# imcontsub_test.py is a Python script that tests the correctness
# of the imcontsub task in CASA.
#
# The tests include:
#    1. Incorrect input for each paramter.  Incorrect input includes
#       one input of the incorrect data type, out-of-bounds (where
#       applicable, and correct data type but non-sensical.
#    2. A set of sample continuum subtractions with expected
#       output
#    3. Calculating one example for each allowed fitorder
#    4. Continuum subtraction with region selection on the sky,
#       channels, and stokes values, as well as using an input
#       region file.
#
# In the imcontsub task a specified continuum channel is subtracted
# from spectral line data.
#
# The expected input is a spectral line image. Both a continuum
# image and a continuum-subtracted spectral line are created as
# output, as long as the user has provided filenames for them.
#
# By default the continuum subtraction is applied to the whole
# image, however, if a region file is given and/or the user specifies
# a box range, channels, or stokes values then the subtraction is
# performed on this portion of the image only.
#
# </synopsis> 
#
# <example>
# # This test was designed to run in the CASA unit test system.
# # This example shows who to run it manually from outside casapy.
# casapy -c runUnitTest test_imcontsub
#
# or
#
# # This example shows who to run it manually from with casapy.
# runUnitTest.main(['imcontsub_test'])
#
# </example>
#
# <motivation>
# To provide a test standard to the imcontsub task to try and ensure
# coding changes do not break the 
# </motivation>
#
# <todo>
# Almost everything!
#
# imcontsub doesn't return anything currently on success or failure.
# If this changes the tests will need to change to check for this.
# The basic design of the tests is based on the immoments test which
# returns an image tool upon success.
# </todo>

#import random
from __future__ import absolute_import
import os
import shutil
import unittest

from casatasks.private.casa_transition import is_CASA6
if is_CASA6:
    from casatools import ctsys, image
    from casatasks import casalog, imcontsub, immath

    _ia = image( )

    datapath = ctsys.resolve('unittest/imcontsub/')
else:
    import casac
    from tasks import *
    from taskinit import *

    _ia = iatool( )
    
    datapath = os.environ.get('CASAPATH').split()[0]+'/casatestdata/unittest/imcontsub/'

# Input files
mylist = ['g192_a2.image', 'g192_a2.image-2.rgn', 
        'g192_a2.contfree', 'g192_a2.cont', 
        'g192_a2.contfree.order1', 'g192_a2.cont.order1', 
        'g192_a2.contfree.order2', 'g192_a2.cont.order2',
        'boxychans_cont.im', 'boxychans_line.im']

# Tests the correctness of the imcontsub task in CASA including:
#   1. Incorrect input for each paramter.  Incorrect input includes
#      one input of the incorrect data type, out-of-bounds (where
#      applicable, and correct data type but non-sensical.
#   2. Doing a couple continuum subtractions checking the 
#      results with previous results. 
#   3. Doing continuum subtraction with fitorder from 1 to ?? 
#      and verifying the results.
#   4. Doing continuum subtraiong with region selection on the sky,
#      channels, and stokes values, as well as using an input
#      region file.
#   
#   Data used for this test includes: 
#        1. g192_a2.image

class imcontsub_test(unittest.TestCase):

    def try_imcs(self, *args, **kwargs):
        passes = False
        # CASA6 tasks throw exceptions, CASA5 tasks return False
        # this test is used for expected errors
        try:
            results = imcontsub(*args, **kwargs)
            if not results:
                passes = True
        except:
            passes = True
        self.assertTrue(passes)

    def setUp(self):
        if(os.path.exists(mylist[0])):
            for f in mylist:
                os.system('rm -rf ' +f)
        
        for f in mylist:
            os.system('cp -RH '+os.path.join(datapath,f)+' '+f)


    def tearDown(self):
        for f in mylist:
            os.system('rm -rf ' +f)
            os.system('rm -rf cont_*')
            os.system('rm -rf input_test*')
            os.system('rm -rf fit_test*')
            os.system('rm -rf line_*')
            
            if os.path.exists( 'garbage.rgn' ):
                os.remove('garbage.rgn')
        
    ####################################################################
    # Incorrect inputs to parameters.  The parameters are:
    #    imagename
    #    linefile
    #    contfile
    #    fitorder
    #    region
    #    box
    #    chans
    #    stokes
    #
    # Returns True if successful, and False if it has failed.
    ####################################################################
    
    def test_bad_imagename(self):
        """Test bad image name throws exception"""
        
        self.try_imcs( 'g192', contfile='input_test_cont1', linefile='input_test_line1' )
        
    def test_bad_linefile(self):
        """ Test bad line file name fails"""
        
        # FIXME shouldn't a bad linefile name throw an exception?
        filename = 'input_test_line1'
        myfile = open(filename, mode="w")
        myfile.write("x")
        myfile.close()
        with self.assertRaises(ValueError):
            imcontsub( 'g192_a2.image', linefile=filename )
            
    def test_bad_contfile(self):
        """ Test bad continuum file name fails"""
        
        # FIXME shouldn't a bad linefile name throw an exception?
        filename = 'input_test_cont'
        myfile = open(filename, mode="w")
        myfile.write("x")
        myfile.close()
        with self.assertRaises(ValueError):
            imcontsub( 'g192_a2.image', contfile=filename )
        
    def test_bad_fitorder(self):
        """Test bad fitorder fails"""
            
        self.try_imcs( 'g192_a2.image', fitorder=-1, contfile='moment_test' )
        self.try_imcs( 'g192_a2.image', fitorder=-2.3, contfile='moment_test' )
        
    def test_bad_region(self):
        """ Test bad region parameter fails"""
        
        self.try_imcs( 'g192_a2.image', region=7 )

        filename = os.getcwd()+'/garbage.rgn'
        self.try_imcs( 'g192_a2.image', region=filename)

        fp=open( filename, 'w' )
        fp.writelines('This file does NOT contain a valid CASA region specification\n')
        fp.close()
        self.try_imcs( 'g192_a2.image', region=filename)


    def test_bad_chans(self):
        """Test bad chans parameter causes failure"""
        
        self.try_imcs( 'g192_a2.image', chans='-5' )
        self.try_imcs( 'g192_a2.image', chans='-2' )
        self.try_imcs( 'g192_a2.image', chans='-18' )
        self.try_imcs( 'g192_a2.image', chans='45' )
        self.try_imcs( 'g192_a2.image', chans='40' )

    def test_bad_box(self):
        """Test bad box parameter causes failure"""

        self.try_imcs( 'g192_a2.image', box='-3,0,511,511' )
        self.try_imcs( 'g192_a2.image', box='0,-3,511,511' )
        self.try_imcs( 'g192_a2.image', box='-2,0,511,511' )
        self.try_imcs( 'g192_a2.image', box='0,-2,511,511' )
        self.try_imcs( 'g192_a2.image', box='0,0,512,511' )
        self.try_imcs( 'g192_a2.image', box='0,0,511,512' )
        self.try_imcs( 'g192_a2.image', box='0, 0,525,511' )
        self.try_imcs( 'g192_a2.image', box='0,0,511,525' )


    def test_bad_stokes(self):
        """Test bad stokes parameter causes failure"""
        self.try_imcs( 'g192_a2.image', stokes='Q' )
        self.try_imcs( 'g192_a2.image', stokes='yellow' )

    
    ## ####################################################################
    ## # Continuum subtraction correctness test.
    ## #
    ## # This test subtacts the continuum from the g192 data file
    ## # and compares the results (both continuum and spectral line
    ## # with subtracted continuum files) with pervious results.
    ## #
    ## # Random values are selected in the files and compared.
    ## # FIXME compare the entire arrays, not bloody random values
    ## # FIXED - replacing it with the equivalent but better test_fitorder(0)
    ## # sped the suite up from 1847s to 1194s on faraday.cv.nrao.edu.
    ## #
    ## # Returns True if successful, and False if it has failed.
    ## ####################################################################
    
    ## def test_continuum(self):
    ##     '''Imcontsub: Testing continuum sub correctness'''
    ##     retValue = {'success': True, 'msgs': "", 'error_msgs': '' }
    ##     casalog.post( "Starting imcontsub CONTINUUM SUB CORRECTNESS tests.", 'NORMAL2' )
    
    ##     try:
    ##         results=imcontsub( 'g192_a2.image', fitorder=0, contfile='cont_test_cont1', linefile='cont_test_line1' )
    ##     except Exception, err:
    ##         retValue['success']=False
    ##         retValue['error_msgs']=retValue['error_msgs']\
    ##                  +"\nError: Unable to subtract continuum with a fit order of 0 "\
    ##                  +"\n\t REULTS: "+str(results)
    ##     else:
    ##         if ( not os.path.exists( 'cont_test_cont1' ) or not os.path.exists( 'cont_test_line1' ) or not results ): 
    ##             retValue['success']=False
    ##             retValue['error_msgs']=retValue['error_msgs']\
    ##                    +"\nError: continuum 3 output files were NOT created."
    ##         else:
    ##             # Now that we know something has been done lets check some values
    ##             # with previously created files to see if the values are the same.
    ##             # We randomly pick 50 points (almost 10%)
    ##             # FIXME lovely, yes let's pick random values to make sure any failures 
    ##             # cannot easily be reproduced, ugh
    ##             for count in range(0,50):
    ##                 x = random.randint(0,511)
    ##                 y = random.randint(0,511)
    ##                 box=str(x)+','+str(y)+','+str(x)+','+str(y)
    ##                 chan = str(random.randint(0,39))
    
    ##                 line_prev_value={}
    ##                 line_cur_value={'empty':''}
    ##                 try: 
    ##                     line_prev_value = imval( 'g192_a2.contfree', box=box, chans=chan, stokes='I' )
    ##                     line_cur_value  = imval( 'cont_test_line1', box=box, chans=chan, stokes='I' )
    ##                 except:
    ##                     retValue['success']=False
    ##                     retValue['error_msgs']=retValue['error_msgs']\
    ##                         +"\nError: Unable to compare spectral line files."
    ##                 else:
    ##                    #print "Spec line prev value: ", line_prev_value
    ##                    #print "spec line current value: ", line_cur_value  
    ##                    casalog.post( "*** line_prev_value " + str(line_prev_value), 'WARN')
    ##                    casalog.post( "*** line_cur_value " + str(line_cur_value), 'WARN')      
    ##                    if ( (line_prev_value['data'] != line_cur_value['data']).any() ):
    ##                     retValue['success']    = False
    ##                     retValue['error_msgs'] = '\nError: spectral line value differs with '\
    ##                           + "previously calculated value at: "\
    ##                           + "\t["+str(x)+','+str(y)+','+chan+',I].'\
    ##                           + "\tvalues are "+str(line_prev_value)+" and "+str(line_cur_value)
    ##                 try:
    ##                     cont_prev_value = imval( 'g192_a2.cont', box=box, chans=chan, stokes='I' )
    ##                     cont_cur_value  = imval( 'cont_test_cont1', box=box, chans=chan, stokes='I' )
    ##                 except:
    ##                     retValue['success']=False
    ##                     retValue['error_msgs']=retValue['error_msgs']\
    ##                         +"\nError: Unable to compare continuum files."
    ##                 else:
    ##                    #print "Continuum prev value: ", cont_prev_value
    ##                    #print "Continuum current value: ", cont_cur_value        
    ##                    if ( (cont_prev_value['data'] != cont_cur_value['data']).any() ):
    ##                     retValue['success']    = False
    ##                     retValue['error_msgs'] = '\nError: continuum value differs with '\
    ##                         + "previously calculated value at: "\
    ##                         + "\t["+str(x)+','+str(y)+','+chan+',I].'
    ##                         #+ "\tvalues are "+str(cont_prev_value)+" and "+str(cont_cur_value)
    
    ##     self.assertTrue(retValue['success'],retValue['error_msgs'])
    
    
    ####################################################################
    # Region selection correction test.
    #
    # This test selects a region for continuum subtraction. Checks
    # are done to make sure only the data in the selected region
    # changes.
    #
    # Returns True if successful, and False if it has failed.
    ####################################################################
    
#    def test_region(self):
#        '''Imcontsub: Testing region parameters'''
#        retValue = {'success': True, 'msgs': "", 'error_msgs': '' }
#        casalog.post( "Starting imcontsub REGION tests.", 'NORMAL2' )
#    
#        # First step get rid of all the old test files!
#        for file in os.listdir( '.' ):
#            if file.startswith( 'rgn_test_' ):
#                shutil.rmtree( file )
#    
#    
#    
#        self.assertTrue(retValue['success'],retValue['error_msgs'])
    
    
    ####################################################################
    # Fitorder tests correctness test.
    #
    # This test subtacts the continuum from the g192 data file
    # for fitorder =1 and fitorder=2, note that fitorder=0 is
    # tested in the continuum test and valid/invalid inputs to
    # the fitorder paramter are tested in the input test.
    #
    # The results, image file contents, are compared with previously
    # created image files.
    #
    # Returns True if successful, and False if it has failed.
    ####################################################################
    
    def test_fitorder(self):
        '''Imcontsub: testing fitorder correctness'''
        retValue = {'success': True, 'msgs': "", 'error_msgs': '' }
        casalog.post( "Starting imcontsub INPUT/OUTPUT tests.", 'NORMAL2' )
    
        # First step get rid of all the old test files!
        for file in os.listdir( '.' ):
            if file.startswith( 'fit_test_' ):
                shutil.rmtree( file )
    
        # TODO I'm just fixing the tests here to do something more reasonable since they were broken
        # and in bad need of rewriting (see the diff with the previous version before I made these
        # edits). This whole test file probably needs to be reviewed but of course time is tight so
        # I cannot do that now.
    
        for order in range(2):
            contfile='fit_test_cont'+str(order)
            linefile='fit_test_line'+str(order)

            if order > 0:
                oldcontfile='g192_a2.cont.order'+str(order)
                oldlinefile='g192_a2.contfree.order'+str(order)
            else:
                oldcontfile = 'g192_a2.cont'
                oldlinefile = 'g192_a2.contfree'
                
            try:
                imcontsub('g192_a2.image', fitorder=order, contfile=contfile, linefile=linefile)
            except Exception as exc:
                retValue['success']=False
                retValue['error_msgs']=retValue['error_msgs']\
                    +"\nError: Unable to subtract continuum with a fit order="+str(order)\
                    +"\n\t Exception: "+str(exc)
            else:
                if os.path.isdir(contfile) and os.path.isdir(linefile):
                    retValue = self.cmp_images(linefile, oldlinefile,
                                               "Order " + str(order) + " line image",
                                               retValue)
                    retValue = self.cmp_images(contfile, oldcontfile,
                                               "Order " + str(order) + " continuum image",
                                               retValue)
                else:  
                    retValue['success']=False
                    retValue['error_msgs']=retValue['error_msgs']\
                       +"\nError: output files were NOT created for fitorder="\
                       +str(order)+" test."

        self.assertTrue(retValue['success'], retValue['error_msgs'])

    def cmp_images(self, newimg, oldimg, errmsg, retval, tol=1.0e-8):
        """
        Check that newimg matches oldimg to within tol.
        errmsg is a descriptive label in case there is an error.
        retval is updated with the results and returned.
        """
        try:
            subtract_expr = '(\"' + newimg + '\"-\"' + oldimg + '\")'
            output_image = newimg + '.diff'
            immath(mode='evalexpr', expr=subtract_expr, outfile=output_image)
            _ia.open(output_image)
            stats = _ia.statistics()
            _ia.close()
            absmax = max(abs(stats['min']), abs(stats['max']))
            # in an infinite precision utopia, the difference image would be 0, but
            # alas, we do not live in such a world yet.
            if (absmax > tol):
                retval['success'] = False
                retval['error_msgs'] += errmsg + ' error: Max residual is ' + str(absmax)
        except Exception as err:
            retval['success'] = False
            retval['error_msgs'] += errmsg + " error: Exception thrown: " + str(err)
        return retval
        

    def test_box_and_chans(self):
        '''Imcontsub: Testing box and chans'''
        retValue = {'success': True, 'msgs': "", 'error_msgs': '' }
        #casalog.post("Starting imcontsub box and chans tests.", 'NORMAL2')

        oldcfil = 'boxychans_cont.im'
        cfil = 'test_' + oldcfil
        oldlfil = 'boxychans_line.im'
        lfil = 'test_' + oldlfil
        bx   = [400, 420, 490, 470]
    
        try:
            imcontsub('g192_a2.image', fitorder=0, contfile=cfil,
                      linefile=lfil, box=bx,
                      chans='32~37')  # Purposely one-sided.  
        except Exception as exc:
            retValue['success']=False
            retValue['error_msgs'] += "\nError: Unable to subtract continuum with box and chans "\
                                      +"\n\t Exception:: "+str(exc)
        else:
            if os.path.isdir(cfil) and os.path.isdir(lfil):
                retValue = self.cmp_images(lfil, oldlfil,
                                           "box and chans line image", retValue)
                retValue = self.cmp_images(cfil, oldcfil,
                                           "box and chans continuum image",
                                           retValue)
            else:
                retValue['success']=False
                retValue['error_msgs'] += "\nError: box and chans output files were NOT created."

        self.assertTrue(retValue['success'], retValue['error_msgs'])
        
def suite():
    return [imcontsub_test]

if is_CASA6:
    if __name__ == '__main__':
        unittest.main()