import sys
import os
import string
import time
from viewertool import viewertool
from taskinit import casalog
from casa_stack_manip import stack_frame_find
###
### if numpy is not available, make float64 and ndarray redundant checks...
###
try:
    from numpy import float64 as float64
    from numpy import ndarray as ndarray
except:
    float64 = float
    ndarray = list

class __imview_class(object):
    "imview() task with local state for created viewer tool"

    def __init__( self ):
        self.local_vi = None
        self.local_ving = None
        self.__dirstack = [ ]
        self.__colorwedge_queue = [ ]

    def __call__( self, raster=None, contour=None, zoom=None, axes=None, out=None ):
        """ Old parameters:
               infile=None,displaytype=None,channel=None,zoom=None,outfile=None,
               outscale=None,outdpi=None,outformat=None,outlandscape=None,gui=None
        The imview task will display images in raster, contour, vector or
        marker form.  Images can be blinked, and movies are available
        for spectral-line image cubes.  For measurement sets, many
        display and editing options are available.

        examples of usage:

        imview
        imview "myimage.im"
        imview "myrestorefile.rstr"
        
        imview "myimage.im", "contour"

        imview "'myimage1.im' - 2 * 'myimage2.im'", "lel"
    
        Executing imview( ) will bring up a display panel
        window, which can be resized.  If no data file was specified,
        a Load Data window will also appear.  Click on the desired data
        file and choose the display type; the rendered data should appear
        on the display panel.

        A Data Display Options window will also appear.  It has drop-down
        subsections for related options, most of which are self-explanatory.
      
        The state of the imview task -- loaded data and related display
        options -- can be saved in a 'restore' file for later use.
        You can provide the restore filename on the command line or
        select it from the Load Data window.

        See the cookbook for more details on using the imview task.
    
        Keyword arguments:
        infile -- Name of file to visualize
            default: ''
            example: infile='ngc5921.image'
            If no infile is specified the Load Data window
            will appear for selecting data.
        displaytype -- (optional): method of rendering data
            visually (raster, contour, vector or marker).  
            You can also set this parameter to 'lel' and
            provide an lel expression for infile (advanced).
            default: 'raster'
            example: displaytype='contour'

        Note: the filetype parameter is optional; typing of
                data files is now inferred.
                example:  imview infile='my.im'
            implies:  imview infile='my.im', filetype='raster'
        the filetype is still used to load contours, etc.


        """
        myf=stack_frame_find( )
        vi = myf['vi'] if myf.has_key('vi') else None
        ving = myf['ving'] if myf.has_key('ving') else None

        casalog.origin('imview')
        
        if (type(out) == str and len(out) != 0) or \
               (type(out) == dict and len(out) != 0) :
            gui = False
            (out_file, out_format, out_scale, out_dpi, out_orientation) = self.__extract_outputinfo( out )
        else:
            gui = True

        if gui and self.local_vi is None or \
           not gui and self.local_ving is None:
            try:
                ## vi/ving might not be defined in taskinit if
                ## loading directly from python via casa.py...
                vwr = vi if gui else ving

                if type(vwr) == type(None) or type(vwr.cwd( )) != str:
                    vwr = viewertool( gui, True, (type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == type(os)) )
            except:
                vwr = None

            if gui:
                self.local_vi = vwr
            else:
                self.local_ving = vwr
        else:
            vwr = self.local_vi if gui else self.local_ving

        if type(vwr) == type(None):
            casalog.post( "failed to find a viewertool...", 'SEVERE')
            raise Exception, "failed to find a viewertool..."

        self.__pushd( vwr, os.path.abspath(os.curdir) )

        if (raster is None or len(raster) == 0) and \
           (contour is None or len(contour) == 0) :
            panel = self.__panel(vwr)
        else:
            panel = self.__load_files( "raster", vwr, None, raster )
            panel = self.__load_files( "contour", vwr, panel, contour )
            
        self.__set_axes( vwr, panel, axes )
        self.__zoom( vwr, panel, zoom )
        self.__process_colorwedges( vwr, panel )
        #vwr.unfreeze( panel )

        if not gui:
            vwr.output(out_file,scale=out_scale,dpi=out_dpi,format=out_format,orientation=out_orientation,panel=panel)
            vwr.close(panel)

        self.__popd( vwr )

        return None

    def __panel( self, vwr ):
        panel = vwr.panel("viewer")
        #vwr.freeze( panel )
        return panel

    def __load_raster( self, vwr, panel, raster ):
        ## here we can assume we have a dictionary
        ## that specifies what needs to be done...
        data = None
        if not raster.has_key('file'):
            return panel

        if  type(raster['file']) != str or not os.path.exists(raster['file']) or \
               vwr.fileinfo(raster['file'])['type'] != 'image':
            casalog.post( str(raster['file']) + " does not exist or is not an image", 'SEVERE')
            raise Exception, raster['file'] + " does not exist or is not an image"

        if panel is None:
            panel = self.__panel(vwr)
        
        scaling = 0.0
        if raster.has_key('scaling'):
            scaling = self.__checknumeric(raster['scaling'], float, "raster scaling")

            
        data = vwr.load( raster['file'], 'raster', panel=panel, scaling=scaling )
        
        if raster.has_key('range'):
            vwr.datarange( self.__checknumeric(raster['range'], float, "data range", array_size=2), data=data )

        if raster.has_key('colormap'):
            if type(raster['colormap']) == str:
                vwr.colormap( raster['colormap'], data )
            else:
                casalog.post( "raster colormap must be a string", 'SEVERE')
                raise Exception, "raster colormap must be a string"

        if raster.has_key('colorwedge'):
            if type(raster['colorwedge']) == bool:
                self.__colorwedge_queue.append( (data,raster['colorwedge']) )
            else:
                casalog.post( "colorwedge must be a boolean", 'SEVERE')
                raise Exception, "colorwedge must be a boolean"

        return panel

    def __process_colorwedges( self, vwr, panel ):
        self.__colorwedge_queue.reverse( )
        while len(self.__colorwedge_queue) > 0:
            element = self.__colorwedge_queue.pop( )
            vwr.colorwedge( element[1], element[0] )

    def __load_contour( self, vwr, panel, contour ):
        ## here we can assume we have a dictionary
        ## that specifies what needs to be done...
        data = None
        if not contour.has_key('file'):
            return panel

        if type(contour['file']) != str or not os.path.exists(contour['file']) or \
               vwr.fileinfo(contour['file'])['type'] != 'image':
            casalog.post( str(contour['file']) + " does not exist or is not an image", 'SEVERE')
            raise Exception, contour['file'] + " does not exist or is not an image"

        if panel is None:
            panel = self.__panel(vwr)

        data = vwr.load( contour['file'], 'contour', panel=panel )

        if contour.has_key('levels'):
            vwr.contourlevels( self.__checknumeric(contour['levels'], float, "contour levels", array_size=0), data=data )
        if contour.has_key('unit'):
            vwr.contourlevels( unitlevel=self.__checknumeric(contour['unit'], float, "contour unitlevel"), data=data )
        if contour.has_key('base'):
            vwr.contourlevels( baselevel=self.__checknumeric(contour['base'], float, "contour baselevel"), data=data )

        try:
            if contour.has_key('thickness'):
                vwr.contourthickness( thickness=self.__checknumeric(contour['thickness'], float, "contour thickness"), data=data )
            if contour.has_key('color'):
                vwr.contourcolor( contour['color'], data=data )
        except:
            print "viewertool error: %s" % sys.exc_info()[1]

        return panel

    def __set_axes( self, vwr, panel, axes ):
        x=''
        y=''
        z=''
        invoke = False
        if type(axes) == list and len(axes) == 3 and \
           all( map( lambda x: type(x) == str, axes ) ) :
            x = axes[0]
            y = axes[1]
            z = axes[2]
            invoke = True
        elif type(axes) == dict :
            if axes.has_key('x'):
                if type(axes['x']) != str:
                    casalog.post( "dimensions of axes must be strings (x is not)", 'SEVERE')
                    raise Exception, "dimensions of axes must be strings (x is not)"
                x = axes['x']
                invoke = True
            if axes.has_key('y'):
                if type(axes['y']) != str:
                    casalog.post( "dimensions of axes must be strings (y is not)", 'SEVERE')
                    raise Exception, "dimensions of axes must be strings (y is not)"
                y = axes['y']
                invoke = True
            if axes.has_key('z'):
                if type(axes['z']) != str:
                    casalog.post( "dimensions of axes must be strings (z is not)", 'SEVERE')
                    raise Exception, "dimensions of axes must be strings (z is not)"
                z = axes['z']
                invoke = True
        else :
            casalog.post( "'axes' must either be a string list of 3 dimensions or a dictionary", 'SEVERE')
            raise Exception, "'axes' must either be a string list of 3 dimensions or a dictionary"

        result = False
        if invoke:
            vwr.axes( x, y, z, panel=panel )
            result = True

        return result


    def __zoom( self, vwr, panel, zoom ) :      

        channel = -1
        if type(zoom) == dict and zoom.has_key('channel') :
            channel = self.__checknumeric(zoom['channel'], int, "channel")

        if type(zoom) == int :
            vwr.zoom(level=zoom,panel=panel)
        elif type(zoom) == str and os.path.isfile( zoom ):
            vwr.zoom(region=zoom,panel=panel)
        elif type(zoom) == dict and zoom.has_key('blc') and zoom.has_key('trc'):
            blc = zoom['blc']
            trc = zoom['trc']
            if type(blc) == list and type(trc) == list:
                blc = self.__checknumeric( blc, float, "zoom blc", array_size=2 )
                trc = self.__checknumeric( trc, float, "zoom trc", array_size=2 )

                coord = "pixel"
                if zoom.has_key('coordinates'):
                    if zoom.has_key('coord'):
                        casalog.post( "cannot specify both 'coord' and 'coordinates' for zoom", 'SEVERE')
                        raise Exception, "cannot specify both 'coord' and 'coordinates' for zoom"
                    if type(zoom['coordinates']) != str:
                        casalog.post( "zoom coordinates must be a string", 'SEVERE')
                        raise Exception, "zoom coordinates must be a string"
                    coord = zoom['coordinates']
                    if coord != 'world' and coord != 'pixel' :
                        casalog.post( "zoom coordinates must be either 'world' or 'pixel'", 'SEVERE')
                        raise Exception, "zoom coordinates must be either 'world' or 'pixel'"
                elif zoom.has_key('coord'):
                    if type(zoom['coord']) != str:
                        casalog.post( "zoom coord must be a string", 'SEVERE')
                        raise Exception, "zoom coord must be a string"
                    coord = zoom['coord']
                    if coord != 'world' and coord != 'pixel' :
                        casalog.post( "zoom coord must be either 'world' or 'pixel'", 'SEVERE')
                        raise Exception, "zoom coord must be either 'world' or 'pixel'"
                if channel >= 0:
                    vwr.channel( channel, panel=panel )
                vwr.zoom(blc=blc,trc=trc, coordinates=coord, panel=panel)
            elif type(blc) == dict and type(trc) == dict and \
                 blc.has_key( '*1' ) and trc.has_key( '*1' ) :
                if channel >= 0:
                    vwr.channel( channel, panel=panel )
                vwr.zoom(region=zoom,panel=panel)
            else:
                casalog.post( "zoom blc & trc must be either lists or dictionaries", 'SEVERE')
                raise Exception, "zoom blc & trc must be either lists or dictionaries" 

        elif type(zoom) == dict and zoom.has_key('regions'):
            if channel >= 0:
                vwr.channel( channel, panel=panel )
            vwr.zoom(region=zoom,panel=panel)
        elif type(zoom) == dict and zoom.has_key('file') and type(zoom['file']) == str and os.path.isfile( zoom['file'] ):
            if channel >= 0:
                vwr.channel( channel, panel=panel )
            vwr.zoom(region=zoom['file'],panel=panel)
        else:
            if channel < 0:
                casalog.post( "invalid zoom parameters", 'SEVERE')
                raise Exception, "invalid zoom parameters"
            else:
                vwr.channel( channel, panel=panel )
        vwr.show(panel=panel)

    def __load_files( self, filetype, vwr, panel, files ):

        if filetype != "raster" and filetype != "contour":
            casalog.post( "internal error __load_files( )...", 'SEVERE')
            raise Exception, "internal error __load_files( )..."

        if type(files) == str:
            panel = self.__load_raster( vwr, panel, { 'file': files } ) if filetype == 'raster' else \
                        self.__load_contour( vwr, panel, { 'file': files } )
        elif type(files) == dict:
            panel = self.__load_raster( vwr, panel, files ) if filetype == 'raster' else \
                        self.__load_contour( vwr, panel, files )
        elif type(files) == list:
            if all(map( lambda x: type(x) == dict, files )):
                for f in files:
                    panel = self.__load_raster( vwr, panel, f ) if filetype == 'raster' else \
                                self.__load_contour( vwr, panel, f )
            elif all(map( lambda x: type(x) == str, files )):
                for f in files:
                    panel = self.__load_raster( vwr, panel, { 'file': f } ) if filetype == 'raster' else \
                                self.__load_contour( vwr, panel, { 'file': f } )
            else:
                casalog.post( "multiple " + str(filetype) + " specifications must be either all dictionaries or all strings", 'SEVERE')
                raise Exception, "multiple " + filetype + " specifications must be either all dictionaries or all strings"
        else:
            casalog.post( filetype + "s can be a single file path (string), a single specification (dictionary), or a list containing all strings or all dictionaries", 'SEVERE')
            raise Exception, filetype + "s can be a single file path (string), a single specification (dictionary), or a list containing all strings or all dictionaries"
        return panel


    def __extract_outputinfo( self, out ):
        output_file=None
        output_format=None
        output_scale=1.0
        output_dpi=300
        output_orientation="portrait"
        
        if type(out) == str:
            output_format = self.__check_filename(out)
            output_file = out

        elif type(out) == dict:
            if out.has_key('file'):
                if type(out['file']) != str:
                    casalog.post( "output filename must be a string", 'SEVERE')
                    raise Exception, "output filename must be a string"
                if out.has_key('format'):
                    if type(out['format']) != str:
                        casalog.post( "output format must be a string", 'SEVERE')
                        raise Exception, "output format must be a string"
                    output_format = self.__check_fileformat( out['format'] )
                    self.__check_filename( out['file'], False )
                else:
                    output_format = self.__check_filename( out['file'] )

                output_file = out['file']

            else:
                casalog.post( "an output dictionary must include a 'file' field", 'SEVERE')
                raise Exception, "an output dictionary must include a 'file' field"

            if out.has_key('scale'):
                output_scale = self.__checknumeric(out['scale'], float, "output scale")

            if out.has_key('dpi'):
                output_dpi = self.__checknumeric(out['dpi'], int, "output dpi")
                output_dpi = int(out['dpi'])

            if out.has_key('orientation'):
                if out.has_key('orient'):
                    casalog.post( "output dictionary cannot have both 'orient' and 'orientation' fields", 'SEVERE')
                    raise Exception, "output dictionary cannot have both 'orient' and 'orientation' fields"
                if type(out['orientation']) != str:
                    casalog.post( "output orientation must be a string", 'SEVERE')
                    raise Exception, "output orientation must be a string"
                if out['orientation'] != 'portrait' and out['orientation'] != 'landscape':
                    casalog.post( "output orientation must be either 'portrait' or 'landscape'", 'SEVERE')
                    raise Exception, "output orientation must be either 'portrait' or 'landscape'"
                output_orientation = out['orientation']

            if out.has_key('orient'):
                if type(out['orient']) != str:
                    casalog.post( "output orient field must be a string", 'SEVERE')
                    raise Exception, "output orient field must be a string"
                if out['orient'] != 'portrait' and out['orient'] != 'landscape':
                    casalog.post( "output orient field must be either 'portrait' or 'landscape'", 'SEVERE')
                    raise Exception, "output orient field must be either 'portrait' or 'landscape'"
                output_orientation = out['orient']

        return (output_file, output_format, output_scale, output_dpi, output_orientation)

    def __checknumeric( self, value, otype, error_string, array_size=None ):
        if array_size is not None:
            if type(array_size) != int:
                casalog.post( "internal error: array_size is expected to be of type int", 'SEVERE')
                raise Exception, "internal error: array_size is expected to be of type int"
            if type(value) != list and not isinstance(value,ndarray):
                casalog.post( error_string + " must be a list", 'SEVERE')
                raise Exception, error_string + " must be a list"
            if array_size > 0 and len(value) != array_size:
                numbers = { '1': 'one', '2': 'two', '3': 'three' }
                casalog.post( error_string + " can only be a " + numbers[str(array_size)] + " element numeric list", 'SEVERE')
                raise Exception, error_string + " can only be a " + numbers[str(array_size)] + " element numeric list"
            if not all(map( lambda x: type(x) == int or type(x) == float or isinstance(x,float64), value )):
                casalog.post( error_string + " must be a numeric list", 'SEVERE')
                raise Exception, error_string + " must be a numeric list"
            return map( lambda x: otype(x), value )
                    
        if type(value) != int and type(value) != float:
            casalog.post( error_string + " must be numeric", 'SEVERE')
            raise Exception, error_string + " must be numeric"

        return otype(value)

    def __check_fileformat( self, ext ):
        supported_files = [ 'jpg', 'pdf', 'eps', 'ps', 'png', 'xbm', 'xpm', 'ppm' ]
        if supported_files.count(ext.lower( )) == 0:
            casalog.post( "output format '" + str(ext) + "' not supported; supported types are: " + str(supported_files), 'SEVERE')
            raise Exception, "output format '" + str(ext) + "' not supported; supported types are: " + str(supported_files)
        return ext.lower( )


    def __check_filename( self, out, check_extension = True ):
        dir = os.path.dirname(out)
        if len(dir) > 0 and not os.path.isdir(dir):
            casalog.post( "output directory (" + str(dir) + ") does not exist", 'SEVERE')
            raise Exception, "output directory (" + str(dir) + ") does not exist"
        file = os.path.basename(out)
        if len(file) == 0:
            casalog.post( "could not find a valid file name in '" + str(out) + "'", 'SEVERE')
            raise Exception, "could not find a valid file name in '" + str(out) + "'"
        (base,ext) = os.path.splitext(file)
        if len(ext) == 0:
            casalog.post( "could not infer the ouput type from file name '" + str(file) + "'", 'SEVERE')
            raise Exception, "could not infer the ouput type from file name '" + str(file) + "'"
        return self.__check_fileformat(ext[1:]) if check_extension else ''

    def __pushd( self, vwr, newdir ):
        try:
            old_path = vwr.cwd( )
        except:
            casalog.post( "imview() failed to get the current working directory [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]", 'SEVERE')
            raise Exception, "imview() failed to get the current working directory [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]"

        self.__dirstack.append(old_path)
        try:
            vwr.cwd(newdir)
        except:
            casalog.post( "imview() failed to change to the new working directory (" + os.path.abspath(os.curdir) + ") [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]", 'SEVERE')
            raise Exception, "imview() failed to change to the new working directory (" + os.path.abspath(os.curdir) + ") [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]"


    def __popd( self, vwr ):
        try:
            vwr.cwd(self.__dirstack.pop( ))
        except:
            casalog.post( "imview() failed to restore the old working directory (" + old_path + ") [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]", 'SEVERE')
            raise Exception, "imview() failed to restore the old working directory (" + old_path + ") [" + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + "]"


imview = __imview_class( )