"""
A gd backend http://newcenturycomputers.net/projects/gdmodule.html
"""


from __future__ import division
import sys, os, math, warnings

try: import gd
except ImportError:
    print >>sys.stderr, 'You must first install the gd module http://newcenturycomputers.net/projects/gdmodule.html'
    sys.exit()

        
    
from matplotlib.backend_bases import RendererBase, \
     GraphicsContextBase, FigureManagerBase, FigureCanvasBase
from matplotlib import verbose
from matplotlib._pylab_helpers import Gcf
from matplotlib.cbook import enumerate, pieces, is_string_like
from matplotlib.colors import colorConverter
from matplotlib.figure import Figure
from matplotlib.transforms import Bbox
from matplotlib.font_manager import fontManager
from matplotlib.numerix import ones, array, nx, asarray
# support old font names
if (os.environ.has_key('GDFONTPATH') and not
    os.environ.has_key('TTFPATH')):
    os.environ['TTFPATH'] = os.environ['GDFONTPATH']




PIXELS_PER_INCH = 96  # constant GD uses for screen DPI


def round(x):
    return int(math.floor(x+0.5))


class RendererGD(RendererBase):
    """
    The renderer handles all the drawing primitives using a graphics
    context instance that controls the colors/styles
    """

    
    # todo: can gd support cap and join styles?
    def __init__(self, im, dpi):
        "Initialize the renderer with a gd image instance"
        self.im = im
        self._cached = {}  # a map from get_color args to colors

        self.width, self.height = im.size()
        self.dpi = dpi


    def get_canvas_width_height(self):
        'return the canvas width and height in display coords'
        return self.width, self.height

    def get_text_width_height(self, s, prop, ismath):
        """
        get the width and height in display coords of the string s
        with fontsize in points
        """

        size = prop.get_size_in_points()
        font = fontManager.findfont(prop)

        scale = self.get_text_scale()
        try:
            llx, lly, lrx, lry, urx, ury, ulx, uly = \
                 self.im.get_bounding_rect(
                font, scale*size, 0.0, (0,0), s)
        except ValueError:
            raise RuntimeError('Could not load font %s.  Try setting TTFFONTPATH to include this font' % fontname)
            
        w = abs(lrx - llx)
        h = abs(lly - uly)
        return w, h

                              
    def flipy(self):
        'return true if y small numbers are top for renderer'
        return True
    

    def draw_arc(self, gc, rgbFace, x, y, width, height, angle1, angle2):
        """
        Draw an arc centered at x,y with width and height and angles
        from 0.0 to 360.0
        """

        center = int(x), self.height-int(y)
        wh = int(width), int(height)
        a1, a2 = int(angle1), int(angle2)
        if rgbFace is not None:
            color = self.get_gd_color( rgbFace )
            self.im.filledEllipse(
                center, wh, color)
        color = self.get_gd_color( gc.get_rgb() )
        self.im.arc(center, wh, a1, a2, color)
        self.flush_clip()

    def draw_line(self, gc, x1, y1, x2, y2):
        """
        Draw a single line from x1,y1 to x2,y2
        """
        self.draw_lines(gc, array([x1, x2]), array([y1, y2]))
        
    def draw_lines(self, gc, x, y):
        """
        x and y are equal length arrays, draw lines connecting each
        point in x, y
        """

        x = x.astype(nx.Int16)
        y = self.height*ones(y.shape, nx.Int16) - y.astype(nx.Int16)  
        style = self._set_gd_style(gc)
        self.im.lines( zip(x,y), style)
        self.flush_clip()

    def draw_point(self, gc, x, y):
        """
        Draw a single point at x,y
        """
        self.im.setPixel((int(x),self.height-int(y)),
                         self.get_gd_color( gc.get_rgb() ))
        self.flush_clip()


    
    def draw_polygon(self, gc, rgbFace, points):
        """
        Draw a polygon.  points is a len vertices tuple, each element
        giving the x,y coords a vertex
        """

        edgecolor = self.get_gd_color( gc.get_rgb() )

        points = [(int(x), self.height-int(y)) for x,y in points]


        if rgbFace is not None:
            facecolor = self.get_gd_color( rgbFace )
            self.im.filledPolygon(points, facecolor)
        else: facecolor = None
        if edgecolor != facecolor:
            self.im.polygon(points, edgecolor)
        self.flush_clip()
        
    def draw_rectangle(self, gc, rgbFace, x, y, width, height):
        """
        Draw a rectangle at lower left x,y with width and height
        If filled=True, fill the rectangle with the gc foreground
        gc is a GraphicsContext instance
        """

        lb = int(x), self.height-int(y)
        ur = int(x+width), self.height-int((y+height))
        edgecolor = self.get_gd_color( gc.get_rgb() )
        
        if rgbFace is not None:
            facecolor = self.get_gd_color( rgbFace )
            self.im.filledRectangle(ur, lb, facecolor)
        else: facecolor = None

        if edgecolor != facecolor:
            self.im.rectangle(ur, lb, edgecolor)
        self.flush_clip()

    def draw_text(self, gc, x, y, s, prop, angle, ismath):            
        """
        Render the text using the RendererGD instance
        """

        size = prop.get_size_in_points()
        font = fontManager.findfont(prop)

        x = int(x)
        y = int(y)

        color = self.get_gd_color( gc.get_rgb() )

        angle *= math.pi/180.0

        scale = self.get_text_scale()
        self.im.string_ft(font, scale*size, angle,
                          (x, y), s, color)
        self.flush_clip()

    def finish(self):
        pass
        #self.im.writePng( file('xx.png', 'w') )

    
    def flush_clip(self):
        imw, imh = self.im.size()
        lb = 0, 0
        ur = imw, imh
        self.im.setClip(ur, lb)        


    def get_gd_color(self, rgb):
        """
        RGB is a unit RGB tuple, return a gd color
        """

        r,g,b = rgb
        rgbi = (int(r*255),int(g*255),int(b*255))
        
        try: return self._cached[rgbi]
        except KeyError: pass

        color = self.im.colorAllocate(  rgbi )

        if color==-1:
            warnings.warn('Unable to allocate color %1.3f, %1.3f, %1.3f; using nearest neighbor' % rgb)
            color = self.im.colorClosest(rgbi)

        self._cached[rgbi] = color
        return color




    def get_text_scale(self):
        """
        Return the scale factor for fontsize taking screendpi and pixels per
        inch into account
        """
        return self.dpi.get()/PIXELS_PER_INCH
    
    def new_gc(self):
        """
        Return an instance of a GraphicsContextGD
        """
        return GraphicsContextGD( self.im, self )

    def _set_gd_style(self, gc):
        color = self.get_gd_color( gc.get_rgb() )
        offset, dashes = gc.get_dashes()

        if dashes is not None:
            pixels = self.points_to_pixels(dashes)
            style = []
            for on, off in pieces(pixels):
                if on<1: on = 1
                else: on = round(on)
                if off<1: off = 1
                else: off = round(off)

                style.extend([color]*on)
                style.extend([gd.gdTransparent]*off)
            self.im.setStyle(style)
            return gd.gdStyled
        else:
            if gc.get_antialiased():
                self.im.setAntiAliased(color)
                return gd.gdAntiAliased
            else:
                self.im.setStyle([color])
                return gd.gdStyled


    def points_to_pixels(self, points):
        """
        convert point measures to pixes using dpi and the pixels per
        inch of the display
        """
        return asarray(points)*(PIXELS_PER_INCH/72.0*self.dpi.get()/72.0)

        
class GraphicsContextGD(GraphicsContextBase):
    """
    The graphics context provides the color, line styles, etc...  See
    the gtk and postscript backends for examples of mapping the
    graphics context attributes (cap styles, join styles, line widths,
    colors) to a particular backend.      """
    def __init__(self, im, renderer):
        """
        Initialize with a gd image
        """
        GraphicsContextBase.__init__(self)
        self.im = im
        self.renderer = renderer
        

    def set_clip_rectangle(self, rectangle):
        GraphicsContextBase.set_clip_rectangle(self, rectangle)
        x,y,w,h = rectangle
        imw, imh = self.im.size()
        lb = int(x), imh-int(y)
        ur = int(x+w), imh-int(y+h)
        self.im.setClip(ur, lb)        

    def set_linestyle(self, style):
        GraphicsContextBase.set_linestyle(self, style)
        offset, dashes = self.dashd[style]
        self.set_dashes(offset, dashes)

    def set_linewidth(self, lw):
        GraphicsContextBase.set_linewidth(self, lw)
        pixels = self.renderer.points_to_pixels(lw)
        if pixels<1: pixels = 1
        else: pixels = round(pixels)
        self.im.setThickness(pixels)
              
########################################################################
#    
# The following functions and classes are for matlab compatibility
# mode (pylab) and implement figure managers, etc...
#
########################################################################


def new_figure_manager(num, *args, **kwargs):
    """
    Add a new figure num (default autoincrement).  For GUI
    backends, you'll need to instantiate a new window and embed
    the figure in it.
    """
    thisFig = Figure(*args, **kwargs)
    canvas = FigureCanvasGD(thisFig)
    manager = FigureManagerGD(canvas, num)
    return manager


class FigureCanvasGD(FigureCanvasBase):
    
    def print_figure(self, filename, dpi=150,
                     facecolor='w', edgecolor='w',
                     orientation='portrait'):

        """
        Render the figure to hardcopy using self.renderer as the
        renderer if neccessary

        filename can be a string filename or writable file instance

        """

        origDPI = self.figure.dpi.get()
        origfacecolor = self.figure.get_facecolor()
        origedgecolor = self.figure.get_edgecolor()

        self.figure.dpi.set(dpi)
        self.figure.set_facecolor(facecolor)
        self.figure.set_edgecolor(edgecolor)

        im = self.draw()

        if is_string_like(filename):
            basename, ext = os.path.splitext(filename)
            if not len(ext): filename += '.png'
 
        im.writePng( filename )

        self.figure.dpi.set(origDPI)
        self.figure.set_facecolor(origfacecolor)
        self.figure.set_edgecolor(origedgecolor)

    def draw(self):
        """
        Draw to a gd image and return the image instance
        
        """

        left, bottom, width, height = self.figure.bbox.get_bounds()
        im = gd.image((int(width), int(height)))

        if not hasattr(im, 'setAntiAliased'):
            raise RuntimeError('gd_requirements_failed')
        renderer = RendererGD(im, self.figure.dpi)
        self.figure.draw(renderer)
        renderer.finish()

        return im

class FigureManagerGD(FigureManagerBase):
    """
    This class manages all the figures for matlab mode
    """
    pass



########################################################################
#    
# Now just provide the standard names that backend.__init__ is expecting
# 
########################################################################

FigureManager = FigureManagerGD

         
