#!/usr/bin/env python
# -*- coding: utf-8 -*-

import PoleUtil
import PolePDF
import base64
import cStringIO

paragrafo = PolePDF.paragrafo
normal_esquerda = PolePDF.normal_esquerda
grande_esquerda = PolePDF.ParagraphStyle('normal', fontSize = 18, leading = 18)

# Conveniência
cf = PoleUtil.convert_and_format
formatar = PoleUtil.formatar
cm = PolePDF.cm
mm = PolePDF.mm

LOGO_PYTHON = ' '

CABECALHO = ("\n"
              "<b>Pole - Python utilities by Junior Polegato</b>\n"
              "This module combines functions for development on a day-to-day\n"
              "Facilitate the use of GTK + for GUI and ReportLab for PDF\n"
              "Send NFe and enjoy its features, including XML")
LOGO = None

# Estilo pradão da tabela
_ESTILO_TABELA = [('GRID', (0,0), (-1,-1),0.5, PolePDF.colors.black),
                  ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
                  ('TOPPADDING', (0,0), (-1,-1), 0),
                  ('RIGHTPADDING', (0,0), (-1,-1), 1*PolePDF.mm),
                  ('LEFTPADDING', (0,0), (-1,-1), 1*PolePDF.mm),
                  ('FONTSIZE', (0,0), (-1,-1), PolePDF.normal_centro.fontSize),
                  ('LEADING', (0,0), (-1,-1), PolePDF.normal_centro.leading),
                  ('BOTTOMPADDING', (0,0), (-1,-1), 1)]

def gerar_pdf(paisagem, data_inicial, data_final, titulo_relatorio,
              cabecalho_colunas, sql, dados, conexao, pdf = None, finalizar = True,
              sumario = None, folha = PolePDF.A4, logo = None, cabecalho_logo = None):
    # Acertando os cabeçalhos das colunas
    cabecalhos = []
    cabecalhos_excluidos = []
    casas_default = PoleUtil.locale.localeconv()['frac_digits']
    for (n, registro) in enumerate(cabecalho_colunas):
        registro = list(registro)

        if registro[1] == 0:
            cabecalhos_excluidos.append(n)
            continue

        if len(registro) < 4:
            registro += [casas_default, '']
        else:
            try:
                registro[3] = int(registro[3])
                if len(registro) == 4:
                    registro.append('')
            except Exception:
                if len(registro) == 4:
                    registro.append(registro[3])
                    registro[3] = casas_default
                try:
                    x = registro[3]
                    registro[3] = int(registro[4])
                    registro[4] = x
                except Exception:
                    registro[3] = casas_default
        cabecalhos.append(registro[:5])
    cabecalhos, larguras, tipos, casas, totalizacao = zip(*cabecalhos)
    # Se não passado um logo, usa o logo do Python
    if not logo and LOGO:
        logo = LOGO
    else:
        logo = cStringIO.StringIO()
        logo.write(base64.b64decode(LOGO_PYTHON))
        logo.seek(0)
    # Se não passado um cabeçalho para ir junto ao logo, usa o do Pole
    if not cabecalho_logo:
        cabecalho_logo = CABECALHO
    # Transformando as larguras de centímentros para pontos no PDF
    larguras = [l * cm for l in larguras]
    # Tipos do Python para tipos do Pole
    novos_tipos = []
    for tipo, _casas in zip(tipos, casas):
        if tipo in ('int', 'long'):
            tipo = 'Inteiro'
        elif tipo == 'float':
            tipo = 'Quebrado %i' % _casas
        elif tipo in ('str', 'unicode'):
            tipo = 'Livre'
        elif tipo == 'bool':
            tipo = 'Sim ou Não'
        elif tipo == 'datetime':
            tipo = 'Data e Hora'
        novos_tipos.append(tipo)
    tipos = novos_tipos
    # Estilo das colunas
    estilos = []
    for tipo in tipos:
        estilo = PolePDF.ParagraphStyle('personalidado', normal_esquerda)
        estilo.alignment = (PolePDF.TA_RIGHT if PoleUtil.tipos[tipo][-1] > 75 else
                            PolePDF.TA_LEFT if PoleUtil.tipos[tipo][-1] < 25 else
                            PolePDF.TA_CENTER)
        estilos.append(estilo)
    dados_tabela = None
    # Tenta carregar dados executando via conexão e instrução SQL, qualquer erro usa os dados padrões passados
    try:
        cursor = conexao.cursor()
        cursor.execute(sql)
        registros = cursor.fetchall()
        cursor.close()
    except Exception:
        if not dados:
            registros = []
        elif isinstance(dados[0][0], (list, tuple)):
            registros, dados_tabela = zip(*dados)
        else:
            registros = dados
    # Imagem do logo
    I = PolePDF.Image(logo)
    I.drawWidth = 13 * mm * I.drawWidth / I.drawHeight
    I.drawHeight = 13 * mm
    # Dimensões
    margens = 2 * cm
    largura_folha = folha[0] - margens
    altura_folha = folha[1] - margens
    if not paisagem:
        paisagem = sum(larguras) > largura_folha
    if paisagem:
        largura_folha = folha[1] - margens
        altura_folha = folha[0] - margens
    # Se tiver um pdf, cria uma nova página, senão cria um pdf
    if pdf:
        pdf.nova_pagina()
    else:
        pdf = PolePDF.PDF(paisagem = paisagem,
                          nome_do_arquivo = "/tmp/" + titulo_relatorio.replace('/','-') + ".pdf",
                          titulo = titulo_relatorio + " - " + data_inicial + " à "+data_final)
    # Logo do relatório apenas na primeira página
    altura_disponivel = altura_folha
    altura_disponivel -= pdf.celula(I.drawWidth, 0.5 * cm, None, [I], borda = False)[3]
    pdf.celula(10 * cm , I.drawHeight, None, [PolePDF.paragrafo(cabecalho_logo, PolePDF.pequena_centro)], borda = False)
    pdf.celula(largura_folha - 10 * cm - I.drawWidth, I.drawHeight, '', '', borda = False)
    # Pula 0,5 cm
    altura_disponivel -= pdf.celula(largura_folha, 0.5 * cm, '', '', borda = False)[3]
    # Título do relatório apenas na primeira página
    altura_disponivel -= pdf.celula(largura_folha, 1, None,
                                    [PolePDF.paragrafo(titulo_relatorio, grande_esquerda)],
                                    borda = False)[3]
    altura_disponivel -= pdf.celula(largura_folha, 1, None,
                                    [PolePDF.paragrafo(data_inicial + " à " + data_final, normal_esquerda)],
                                    borda = False)[3]
    # Pula 0,5 cm
    altura_disponivel -= pdf.celula(largura_folha, 0.5 * cm, '', '', borda = False)[3]
    # Altura do cabeçalho das colunas do relatório
    altura_cabecalho = 0
    for largura, cabecalho in zip(larguras, cabecalhos):
        altura_cabecalho = max(altura_cabecalho,
                               pdf.altura(largura, 1, None,
                                          [PolePDF.paragrafo(cabecalho, PolePDF.normal_centro)]))
    altura_disponivel -= altura_cabecalho

    # Transforma em parágrafo cada valor de cada campo, utilizando caracter adequado para booleano
    if dados_tabela:
        # Caracteres para bool: ✔⊠☑✅✓❌❎⨯☒⬚❏⬜'
        dados_tabela = [[paragrafo(('✔' if campo else '❏') if type(campo) == bool else campo, estilo)
                                 for campo, estilo in zip(remove_columns(linha, cabecalhos_excluidos), estilos)]
                                 for linha in dados_tabela]
    else:
        dados_tabela = []
        for registro in registros:

            remove_columns(registro, cabecalhos_excluidos)

            linha = []
            for campo, tipo, estilo in zip(registro, tipos, estilos):
                if type(campo) == bool:
                    conteudo = '✔' if campo else '❏'
                else:
                    try:
                        conteudo = formatar(campo, tipo)
                    except ValueError:
                        conteudo = '' if campo is None else str(campo)

                linha.append(paragrafo(conteudo, estilo))
            dados_tabela.append(linha)
    # Totalização, com exceção de percentual
    totalizado = []
    for n, t in enumerate(totalizacao):
        t = t.lower()
        if t == 'len':
            totalizado.append(formatar(len(registros), 'Inteiro'))
        elif t == 'set':
            totalizado.append(formatar(len(set(zip(*registros)[n])), 'Inteiro'))
        elif t == 'sum':
            totalizado.append(formatar(sum(zip(*registros)[n]), tipos[n]))
        else:
            totalizado.append('')
    # Totalização de percentual
    for n, t in enumerate(totalizacao):
        if t.lower()[:4] in ('perc', 'porc'):
            t, a, b = t.rsplit(' ', 2)
            a = cf(totalizado[int(a)], float)[0]
            b = cf(totalizado[int(b)], float)[0]
            if t.lower()[:4] == 'perc':
                t = 'Porcentagem %i' % PoleUtil.locale.localeconv()['frac_digits']
            totalizado[n] = formatar((a / b) * 100 - 100, t)
    if sum(map(lambda x: x != '', totalizado)):
        # Linha separadora com "Totais" na primeira coluna
        dados_tabela.append([PolePDF.paragrafo("Totais", PolePDF.normal_esquerda)] + [''] * (len(tipos) - 1))
        # Inclusão do totalizado no final dos dados
        dados_tabela.append([paragrafo(t, e) for t, e in zip(totalizado, estilos)])
    # Separação dos dados de cada página
    tabelas = []
    while len(dados_tabela):
        if tabelas:
            altura_disponivel = altura_folha - altura_cabecalho
        t, i, alt_ult_tab = pdf.tabela(sum(larguras), altura_disponivel, dados_tabela, larguras, style=_ESTILO_TABELA)
        tabelas.append((t, alt_ult_tab))
        dados_tabela = dados_tabela[i:]
    # Calcular altura do sumário
    if sumario:
        altura_linha_sumario = 0
        altura_sumario = 1 * cm
        lagura_sumario = 0
        ult_alterada = 0
        for n, celula in enumerate(sumario):
            lagura_sumario += celula[0]
            if lagura_sumario > largura_folha:
                altura_sumario += altura_linha_sumario
                for cel in sumario[ult_alterada:n]:
                    cel[1] = altura_linha_sumario
                ult_alterada = n
                altura_linha_sumario = 0
                lagura_sumario = celula[0]
            altura_linha_sumario = max(altura_linha_sumario, pdf.altura(*celula))
        altura_sumario += altura_linha_sumario
        for cel in sumario[n:]:
            cel[1] = altura_linha_sumario
        # Se o sumário não couber na página que tem a última tabela, soma outra página
        nova_pagina_sumario = (altura_disponivel - alt_ult_tab - altura_sumario) < 0
    else:
        nova_pagina_sumario = False
    # Total de páginas
    paginas = len(tabelas) + nova_pagina_sumario
    str_pagina = "Página: %%s de %s" % cf(paginas, int)[1]
    str_agora = cf(PoleUtil.datetime.datetime.now(), PoleUtil.datetime.datetime)[1]
    # Renderização das tabelas
    for pagina, (tabela, alt_tab) in enumerate(tabelas):
        # Número da página
        pagina += 1
        pdf.celula(largura_folha, 1, None,
                   [PolePDF.paragrafo(str_pagina % cf(pagina, int)[1], PolePDF.pequena_direita)],
                   borda = False, posicao = (1 * cm, 0.55 * cm))
        pdf.celula(largura_folha, 1, None,
                   [PolePDF.paragrafo(str_agora, PolePDF.pequena_direita)],
                   borda = False, posicao = (1 * cm, 0.73 * cm))
        # Cabeçalho das colunas
        for largura, cabecalho, estilo in zip(larguras, cabecalhos, estilos):
            pdf.celula(largura, altura_cabecalho, None, [PolePDF.paragrafo(cabecalho, estilo)])
        # Tabela
        pdf.celula(sum(larguras), alt_tab, None, [tabela], borda = True, espacamento=0)
        # Nova página
        if pagina < paginas:
            pdf.nova_pagina()
    # Renderização do sumário
    if sumario is not None:
        if nova_pagina_sumario:
            # Número da página
            pagina += 1
            pdf.celula(largura_folha, 1, None,
                       [PolePDF.paragrafo(str_pagina % cf(pagina, int)[1], PolePDF.pequena_direita)],
                       borda = False, posicao = (1 * cm, 0.55 * cm))
            pdf.celula(largura_folha, 1, None,
                       [PolePDF.paragrafo(str_agora, PolePDF.pequena_direita)],
                       borda = False, posicao = (1 * cm, 0.73 * cm))
        else:
            # Pula 1 cm
            pdf.celula(largura_folha, 1 * cm, '', '', borda = False)
        # Rederiza as céluas do sumário
        for celula in sumario:
            pdf.celula(*celula)
    # Caso solicitado a finalização, salva o pdf e mostrar para o usuário
    if finalizar:
        pdf.salvar()
        pdf.mostrar()
    # Retorna o pdf criado
    return pdf

LINHA = 0
PIZZA = 1
BARRAS = 2
ASCENDENTE = False
DESCENDENTE = True


def remove_columns(tuple, columns):
    row = list(tuple)

    for column in reversed(columns):
        row.pop(column)

    return row


def grafico(rotulos_valores, tipo = PIZZA, tamanho = (500, 500),
            rgb_fundo = (1, 1, 0.9), rgb_retangulo = (0.9, 0.9, 0.9),
            rgb_conteudo = (0, 0, 0.5), rgb_texto = (1, 1, 0),
            fundo_escala = None, percentual = True, ordem = None):
    "Retorna um gráfico em PNG dentro de um cStringIO"
    # Suporte gráfico com Cairo
    import cairo
    from math import pi, log
    largura, altura = tamanho
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, largura, altura)
    cr = cairo.Context(surface)
    # Fundo branco
    cr.set_source_rgb(*rgb_fundo)
    cr.paint()
    if ordem is not None:
        rotulos_valores = sorted(rotulos_valores, key=lambda x: x[1], reverse = ordem)
    if tipo == PIZZA:
        # Círculo, raio 5% menor
        raio = min(largura, altura) * 0.475
        cr.translate(largura / 2., altura / 2.)
        cr.set_source_rgb(*rgb_conteudo)
        cr.arc(0, 0, raio, 0, 2 * pi)
        cr.fill()
        cr.stroke()
        # Divisão do círculo
        cr.set_line_width(raio / 100.)
        cr.set_source_rgb(0, 0, 0)
        p = 0
        total = float(sum(zip(*rotulos_valores)[1]))
        for rotulo, valor in rotulos_valores:
            percentual = valor / total
            cr.move_to(0, 0)
            cr.arc(0, 0, raio, p * 2 * pi, (p + percentual) * 2 * pi)
            cr.save()
            if percentual:
                texto = "%s %.2f%%" % (rotulo, percentual * 100.)
            else:
                texto = "%s %.2f%%" % (rotulo, valor)
            size = raio / 10.
            height = size
            altura_maxima = 0
            while height - 2 > altura_maxima:
                cr.set_font_size(size)
                x_bearing, y_bearing, width, height, x_advance, y_advance = cr.text_extents(texto)
                distancia = 0.95 * raio - width
                altura_maxima = percentual * pi * distancia
                size -= 1
            angulo = (p + percentual / 2) * 2 * pi
            if pi / 2 < angulo < 3 * pi / 2:
                cr.rotate(angulo + pi)
                cr.move_to(-0.95 * raio - x_bearing, height/2.)
            else:
                cr.rotate(angulo)
                cr.move_to(0.95 * raio - width - x_bearing, height/2.)
            cr.set_source_rgb(*rgb_texto)
            cr.show_text(texto)
            cr.restore()
            p += percentual
        cr.stroke()
    else: # BARRAS ou LINHAS desenha o retângulo
        maior = max(zip(*rotulos_valores)[1])
        if not fundo_escala:
            exp = 10. ** int(log(maior, 10))
            fundo_escala = (int(maior / exp) + 1) * exp
        parte_escala = fundo_escala / 10.
        if maior > fundo_escala * 0.95:
            partes = int(maior / parte_escala) + 1
            escala = partes * parte_escala
            if maior > escala * 0.95:
                escala += parte_escala
                partes += 1
        else:
            escala = fundo_escala
            partes = 10
        cr.translate(0.025 * largura, 0.975 * altura)
        n = len(rotulos_valores)
        w = largura * 0.95
        wo = w / (n * 3 + 1)
        wi = wo * 2
        h = -altura * 0.95
        cr.set_line_width(-h / 200.)
        cr.rectangle(0, 0, w, h)
        cr.set_source_rgb(*rgb_retangulo)
        cr.fill()
        cr.stroke()
        cr.rectangle(0, 0, w, h)
        cr.set_source_rgb(0, 0, 0)
        cr.stroke()
        cr.set_line_width(-h / 1000.)
        for i in range(1, partes):
            cr.move_to(0, h * i / partes)
            cr.line_to(w, h * i / partes)
        cr.stroke()
        for i, (rotulo, valor) in enumerate(rotulos_valores):
            cr.rectangle(wo + (wi + wo) * i, 0, wi, valor/escala * h)
            cr.set_source_rgb(*rgb_conteudo)
            cr.fill()
            cr.stroke()
            cr.save()
            cr.set_font_size(wo)
            x_bearing, y_bearing, width, height, x_advance, y_advance = cr.text_extents(rotulo)
            if valor / escala * h - wo - x_advance - 20 > h:
                cr.move_to(wo + (wi + wo) * i + wi / 2 + height / 2, valor / escala * h - wo)
            else:
                cr.move_to(wo + (wi + wo) * i + wi / 2 + height / 2, valor / escala * h + wo + x_advance)
            cr.rotate(-pi / 2)
            cr.set_source_rgb(*rgb_texto)
            cr.show_text(rotulo)
            cr.restore()

    # Retornar PNG em cStringIO
    png_file = cStringIO.StringIO()
    surface.write_to_png(png_file)
    png_file.seek(0)
    return png_file

# Exemplo de uso
if  __name__ == "__main__":
    pdf = gerar_pdf(None,
              '01/01/2011',
              '31/03/2011',
              "Relatórios de Recursos Cadastrado",
              [['Cód. Recurso', 1.5, 'str'],
               ['Descrição', 1.5, 'str', 'set'],
               ['Valor Hora 1', 3, 'str', 'len'],
               ['Valor Hora 2', 3.5, 'str'],
               ['Valor Hora 3', 6, 'int', 'sum'],
               ['Valor Hora 4', 3, 'float', '3', 'sum'],
               ['Valor Hora 5', 3, 'int', 'perc 4 5'],
               ['Valor Hora 6', 3, 'Quebrado 2', 'sum'],
               ['Ativo', 3, 'bool', 'set'],
              ],
              '',
              [[123,'Hora 1',5465,123465,1,2,3,4,True] ,
               [123,'Hora 2',5465,123465,1,2,3,4,False],
               [123,'Hora 3',5465,123465,1,2,3,100,True] ,
               [123,'Hora 4',5465,123465,1,2,3,4,True]] * 25,
              None,
              sumario = [[5 * cm, 1 * cm, "Titulo 1", "Inform 24"],
                         [7 * cm, 0.1 * cm, "Titulo 2", "Inform 23"],
                         [9 * cm, 0.1 * cm, "Titulo 3", "Inform 22"],
                         [6.7 * cm, 0.1 * cm, "Titulo 4", "Inform 21"],
                         [5 * cm, 0.1 * cm, "Titulo 5", "Inform 20"],
                         [7 * cm, 0.1 * cm, "Titulo 6", "Inform 19"],
                         [9 * cm, 0.1 * cm, "Titulo 7", "Inform 18"],
                         [6.7 * cm, 5 * cm, "Titulo 8", "Inform 17"],
                         [5 * cm, 0.1 * cm, "Titulo 9", "Inform 16"],
                         [7 * cm, 0.1 * cm, "Titulo 10", "Inform 15"],
                         [9 * cm, 0.1 * cm, "Titulo 11", "Inform 14"],
                         [6.7 * cm, 0.1 * cm, "Titulo 12", "Inform 13"],
                        ],
            finalizar  = False)

    pdf.celula(10 * cm, 5 * cm, "", "Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- Este é um teste que vai ter que ficar lá ---- ")#, alinhamento = "direita")
    pdf.celula(3 * cm, 1 * cm, "", "Abaixo terá os gráficos ao lado em tamanho grande")

    png = grafico([['Ígor', 61245.67],
                   ['Claudio', 234567.89],
                   ['Élio', 12345.67],
                   ['Alexandre', 54654.45],
                   ['Freddy', 439224.24]],
                  tipo = PIZZA,
                  ordem = DESCENDENTE)

    png2 = grafico([['Ígor', 61245.67],
                    ['Claudio', 234567.89],
                    ['Élio', 12345.67],
                    ['Alexandre', 54654.45],
                    ['Freddy', 439224.24]],
                   tipo = BARRAS,
                   tamanho = (850, 600),
                   ordem = None,
                   #rgb_fundo = (0,0,0),
                   )

    # Para ver a imagem gerada e tamanho num arquivo
    open('/tmp/pole_relatorio.png', 'wb').write(png.getvalue())
    open('/tmp/pole_relatorio2.png', 'wb').write(png2.getvalue())

    img = PolePDF.Image(png)
    img.drawWidth = 5 * cm
    img.drawHeight = img.drawWidth
    pdf.celula(img.drawWidth, 1 * cm, None, [img], borda = True)

    img2 = PolePDF.Image(png2)
    img2.drawWidth *= 7 * cm / img2.drawHeight
    img2.drawHeight = 7 * cm
    pdf.celula(img2.drawWidth, 1 * cm, None, [img2], borda = True)

    pdf.nova_pagina()
    img.drawWidth = 20 * cm
    img.drawHeight = img.drawWidth
    pdf.celula(img.drawWidth, 1 * cm, None, [img], borda = False,
               posicao = ((pdf.canvas._pagesize[0] - img.drawWidth) / 2,
                          (pdf.canvas._pagesize[1] - img.drawHeight) / 2))

    pdf.nova_pagina()
    img2.drawWidth *= 20 * cm / img2.drawHeight
    img2.drawHeight = 20 * cm
    pdf.celula(img2.drawWidth, 1 * cm, None, [img2], borda = False,
               posicao = ((pdf.canvas._pagesize[0] - img2.drawWidth) / 2,
                          (pdf.canvas._pagesize[1] - img2.drawHeight) / 2))

    pdf.salvar()
    pdf.mostrar()

