#!/usr/bin/python

# tz_google_chart.py
#       --copyright--                   Copyright 2009 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       February 20, 2009       bar
#       April 15, 2009          bar     don't allow ymin and ymax to be the same
#                                       legend
#       August 27, 2009         bar     allow colors to be passed in
#       August 19, 2010         bar     get main program working again
#       September 28, 2011      bar     try POST, does not work
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       October 15, 2013        bar     just fail if pygooglechart isn't in the system
#       January 18, 2016        bar     except syntax change
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_google_chart
#
#
#       Wrapper for pygooglechart.py
#
#


import  httplib
import  socket
import  urllib2
import  urlparse

try :
    import  pygooglechart
except  ImportError :
    pygooglechart   = None

import  httppost
import  replace_file
import  tzlib



WIDTH           = 600


def get_chart(title = None, width = None, hite = None, bnames = None, lnames = None, tnames = None, rnames = None, legend = None, legend_position = None, xfunc = None, yfuncs = None, ymin = None, ymax = None, colors = []) :

    if  not pygooglechart :
        return(None)

    title       = title or ""

    width       = round(width or WIDTH)
    hite        = round(hite  or tzlib.golden_smaller(width))

    bnames      = bnames or [ "" ]
    lnames      = lnames or [ "" ]
    tnames      = tnames or [ "" ]
    rnames      = rnames or [ "" ]

    if  xfunc == None :
        xfunc   = range(0, int(width))

    if  (yfuncs == None) or (not len(yfuncs)) :
        yfuncs  = [ lambda x : x ]

    sas         = []
    for f in yfuncs :
        sas.append( [ f(x) for x in xfunc ] )

    if  ymax   == None :
        ymax    = max([ max(sa) for sa in sas])
    if  ymin   == None :
        ymin    = min([ min([ s for s in sa if s != None ]) for sa in sas])
    if  ymin    > ymax :
        ymin, ymax  = ymax, ymin
    if  ymin   == ymax :
        ymax    = ymin + 1


    thick       = 4
    if  width   < WIDTH :
        thick   = 2

    while True  :

        ln      = len(sas[0])

        #
        #
        #       Start making the chart
        #
        #
        ch      = pygooglechart.SimpleLineChart(int(width),
                                                int(hite),
                                                y_range     = [ ymin, ymax ],
                                                title       = title,
                                               )

        if  hasattr(ch, 'set_title_style') :
            ch.set_title_style("000000", 20)                                                    # if this is our patched code, make the title blacker


        #
        #
        #       Draw the lines
        #
        #
        colors  = colors or [ "000000", "0000ff", "00ff00", "00ffff", ]
        ci      = 0
        lcolors = []
        dis     = xrange(0, ln, int(max(1, ln / width)))
        da      = []
        for sa in sas :
            da.append([ ((s == None) and s) or s for s in [ (sa[si] or 0.0) for si in dis ] ])
            di  = ch.add_data(da[-1])                                                           # Note: pygooglechart does not copy 'd' but rather remembers it directly.
            ch.set_line_style(di, thick)
            lcolors.append(colors[ci % len(colors)])
            ci += 1
        ch.set_colours(lcolors)


        ai      = ch.set_axis_labels(pygooglechart.Axis.RIGHT,  rnames)
        ch.set_axis_style(ai, "0000c0", alignment = 0)

        ai      = ch.set_axis_labels(pygooglechart.Axis.TOP,    tnames)
        ch.set_axis_style(ai, "0000c0", alignment = 0)

        ai      = ch.set_axis_labels(pygooglechart.Axis.LEFT,   lnames)
        ch.set_axis_style(ai, "0000c0", alignment = -1)                                         # note: alignment doesn't work here for some reason

        ai      = ch.set_axis_labels(pygooglechart.Axis.BOTTOM, bnames)
        ch.set_axis_style(ai, "0000c0", alignment = 0)

        if  legend != None :
            ch.set_legend(legend)
            if  legend_position != None :
                ch.set_legend_position(legend_position)
            pass


        ch.fill_solid("bg", "efefef")                                                           # background color (light grey)


        url     = ch.get_url()
        try :
            ul  = len(url)
        except AssertionError :
            print "Google URL too long", ln,
            break                                                                               # probably the vertical scale is from N to N because we have 1 sample, which pygooglechart doesn't like

        if  ul <= 1800 :
            try :
                if  False   :
                    # Build the request
                    t       = list(urlparse.urlsplit(url))

                    form    = httppost.a_multipart_form()
                    for k, v in urlparse.parse_qsl(t[3]) :
                        form.add_field(k, v)

                    t[3]    = ""
                    url     = urlparse.urlunsplit(t)

                    req     = urllib2.Request(url)
                    req.add_header('User-agent', 'tz_google_chart.a_multipart_form')
                    body    = str(form)
                    req.add_header('Content-type', form.get_content_type())
                    req.add_header('Content-length', len(body))
                    req.add_data(body)

                    opener  = urllib2.urlopen(req)
                else        :
                    #
                    #       Code from ch.download() without storing to the file
                    #
                    opener  = urllib2.urlopen(url)

                if opener.headers['content-type'] != 'image/png':
                    raise pygooglechart.BadContentTypeException('Server responded with a content-type of %s' % opener.headers['content-type'])

                fd      = opener.read()

            except socket.error :
                fd      = None
            except socket.herror :
                fd      = None
            except socket.gaierror :
                fd      = None
            except         OSError as (errno, strerror) :
                fd      = None
            except         IOError :                                                            # HTTPError is raised by urllib2 on 404's. But it doesn't come with info - and somehow IOError gets triggered ok
                fd      = None
            except     MemoryError :                                                            # we've run out of memory during the read or what?
                fd      = None
            except  AttributeError :                                                            # something is very wrong with the server
                fd      = None
            except httplib.HTTPException :
                fd      = None
            except pygooglechart.BadContentTypeException :
                fd      = None

            return(fd)


        if width < 100 :
            break

        width  /= 2.0
        hite   /= 2.0
        thick   = max(1, thick - 1)


    return(None)



def write_chart(file_name, ch) :
    if  ch :
        tfn         = file_name + ".tmp"
        tzlib.write_whole_binary_file(tfn, ch)
        replace_file.replace_file(file_name, tfn, file_name + ".bak")
    pass



help_str    = """
%s (options) output.png

Create filtered .xls file and graph in .png file from scope .csv file.
Output file name will have channel number appended to its base name.

Options:
    --xfunc         python_code Set the X axis function     (default: range(0, width))
    --yfunc         python_code Add an eval()-able string to generate Y data (e.g. lambda x: x*2).

    --title         title       Set the graph title.

    --bname         name        Add a X, bottom axis name.
    --lname         name        Add a Y, left axis name.
    --tname         name        Add a top name.
    --rname         name        Add a right side name.

    --width         pixels      Set the graph width.        (default: %u)
    --height        pixels      Set the graph height.       (default: %u)

"""


if __name__ == '__main__' :

    import  os
    import  sys

    import  TZCommandLineAtFile


    pn      = os.path.basename(sys.argv[0])

    del(sys.argv[0])

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    title   = ""
    bnames  = []
    lnames  = []
    tnames  = []
    rnames  = []
    width   = WIDTH
    hite    = int(tzlib.golden_smaller(width))
    xfunc   = None
    yfuncs  = []


    if  tzlib.array_find(sys.argv, [ "--help", "-h", "-?", "/?", "-?" ] ) >= 0 :
        print help_str % ( pn, int(width), int(hite) )
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--title", "-t", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        title   = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--yfunc", "-f", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        f   = eval(sys.argv.pop(oi))
        if  isinstance(f, basestring) :
            xfunc   = eval(f)
        yfuncs.append(f)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--xfunc", "--xf", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        xfunc   = eval(sys.argv.pop(oi))
        if  isinstance(xfunc, basestring) :
            xfunc   = eval(xfunc)
        pass


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--width", "--width", "-w" ])
        if  oi < 0 :    break
        del sys.argv[oi]
        width   = int(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--height", "--hite", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        hite    = int(sys.argv.pop(oi))


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--bname", "--xname", "--bottom", "--bot", "--south", "-x", "-b", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        bnames.append(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--lname", "--yname", "--left", "--west", "-y", "-l", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        lnames.append(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--tname", "--top", "--north", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        tnames.append(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--rname", "--right", "--rite", "--east", "-r", ])
        if  oi < 0 :    break
        del sys.argv[oi]
        rnames.append(sys.argv.pop(oi))


    if  not sys.argv :
        print "Please tell me an output file name!"
        sys.exit(101)

    ofile_name  = sys.argv.pop(0)
    if  ofile_name.startswith("-") :
        print "Sorry. I don't output to files with names beginning with dashes! Put the path in front of the file name, %s."  % ofile_name
        sys.exit(102)

    if  sys.argv :
        print "Extra command line parameters [%s]...!" % sys.argv[0]
        sys.exit(111)


    if  False and not len(yfuncs) :
        print "I'll need some function or data to eval and graph!"
        sys.exit(103)

    if os.path.splitext(ofile_name)[1].lower() != ".png" :
        print "I only output to .png files!"
        sys.exit(104)

    png = get_chart(title = title, width = width, hite = hite, bnames = bnames, lnames = lnames, tnames = tnames, rnames = rnames, xfunc = xfunc, yfuncs = yfuncs)
    if  not png :
        print "No graph returned"
    write_chart(ofile_name, png)


#
#
# eof
