#captcha.py by Pedro Izecksohn.
#version: 20260902 17:06 UTC-03:00
#Local: Rio de Janeiro, RJ, Brazil
#License: You must forgive any mistake and may not modify this code.

from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
from flask import Flask, request, Response
from pathlib import Path
import time
import random
import sys
import os
from threading import Thread
import qbit

baseAddress = None
app = None

MODE = (19,19)
FONT_SIZE=8

BG_COLOR = 1
IMG_FRONT_COLOR = 0
FONT_COLOR=(0,0,0)

def criar_imagem_caractere (tamanho_imagem, tamanho_fonte, caractere, posicao):
    imagem = Image.new("1", tamanho_imagem, BG_COLOR)
    draw = ImageDraw.Draw(imagem)
    fonte = ImageFont.load_default (FONT_SIZE)
    draw.text(posicao, caractere, fill=IMG_FRONT_COLOR, font=fonte)
    return imagem

all_inn = {"A":None, "B":None, "C":None, "D":None, "E":None, "F":None, "G":None, "H":None, "I":None, "J":None, "K":None, "L":None, "M":None, "N":None, "O":None, "P":None, "Q":None, "R":None, "S":None, "T":None, "U":None, "V":None, "W":None, "X":None, "Y":None, "Z":None}
for k in all_inn.keys():
    CHSZ = FONT_SIZE
    l=list()
    all_inn[k]=l
    for y in range(MODE[1]):
        if MODE[0]-y<CHSZ:
            break
        for x in range(MODE[0]):
            if MODE[0]-x<CHSZ:
                break
            l.append (criar_imagem_caractere (MODE, CHSZ, k, (x,y)))

def get_random_image():
    keys = list(all_inn.keys())
    k = random.choice (keys)
    return random.choice (all_inn[k])

def getCaptcha():
    #random.seed (time.time())
    filename = "captchas" + os.sep+ str(random.randint(1,1024)) + ".png"
    path = Path(filename)
    if path.exists():
        print (f"{filename} exists. Try again.", file=sys.stderr)
        return None
    print (f"Saving {filename}", file=sys.stderr)
    img = get_random_image()
    if img is None:
        print ("img = get_random_image(): img is None.", file=sys.stderr)
    try:
        img.save (path, format="PNG", quality=100)
    except Exception as e:
        print (f"Aborting: {str(e)}", file=sys.stderr)
        return None
    return [filename]

neutral = qbit.Qbit(qbit.Qbit.neutral)
false = qbit.Qbit(qbit.Qbit.false)
true = qbit.Qbit(qbit.Qbit.true)

class Info:
    def __init__(self, filename, description:str|None=None):
        self.filename=filename
        self.description=description

def recognize (info:Info):
    filename=info.filename
    path = Path(filename)
    try:
        buffer = BytesIO (path.read_bytes())
    except Exception as e:
        print (f"path.read_bytes() failed: {str(e)}", file=sys.stderr)
        return
    try:
        cptimg = Image.open (buffer, "r", ["PNG"])
    except Exception as e:
        print (f"Image.open failed: {str(e)}", file=sys.stderr)
        return
    try:
        cptimg = cptimg.convert("1")
    except Exception as e:
        print (f"cptimg.convert to 1 failed: {str(e)}", file=sys.stderr)
        return
    cptim= ImageDraw.Draw(cptimg,"1").im
    for k,v in all_inn.items():
        l=v
        #print (f"len(l)={len(l)}")
        for img in l:
            imgim = ImageDraw.Draw(img,"1").im
            qb = neutral
            if (img.width==cptimg.width) and (img.height==cptimg.height):
                #print("sizes are equal.")
                for y in range(img.height):
                    for x in range(img.width):
                        px0 = imgim[y*img.height+x]*255
                        px1 = cptim[y*cptimg.height+x]
                        if px0!=px1:
                            #print (f"{px0}  {px1}")
                            qb = false
                            break
                        qb = true
                        #print(qb)
                        #exit()
                    if qb==false:
                        break
            else:
               print(f"captcha {filename}: sizes are different.", file=sys.stderr)
               continue
            if qb!=true:
                continue
            try:
                Path (filename).unlink()
            except Exception as e:
                print (f"I could not unlink {filename} : {e}", file=sys.stderr)
            #print (f"k={k}")
            info.description=k
            return
    print (f"recognize: Invalid image: {filename}", file=sys.stderr)
    return

class Solver (Thread):
    def __init__(self, info:Info):
        super().__init__ (group=None, target=recognize, name="captcha.Solver", args=[info], kwargs=dict())
        self.info = info
        
def getHTML():
    #print ("Inside getHTML()")
    side = request.args.get("side")
    ttt = request.args.get("ttt")
    nn = request.args.get("nn")
    pid = request.args.get("pid")
    capinfo = getCaptcha()
    if capinfo is None:
        return "Error capinfo is None: Try again."
    ret=f'''<html><head><title>PGS captcha</title></head>
    <body><h1>PGS captcha</h1>
    <h2>By: Pedro Izecksohn</h2>
    <img src="{baseAddress}captcha_png?file={capinfo[0]}" /><br>
    <form method="GET" action="{baseAddress}links" />
    <input type="hidden" name="side" value="{side if side else ''}" />
    <input type="hidden" name="ttt" value="{ttt if ttt else ''}" />
    <input type="hidden" name="nn" value="{nn if nn else ''}" />
    <input type="hidden" name="pid" value="{pid if pid else ''}" />
    <input type="hidden" name="file" value="{capinfo[0]}" />
    <label for="field">Identify the character:</label><br>
    <input id="field" type="text" name="tipo">
    <input type="submit">
    </form>
    </body></html>'''
    return ret

def retPNG():
    filename=request.args.get("file")
    beginning="captchas/"
    ending=".png"
    if not filename:
        return "Not filename."
    if ".." in filename:
        return ".. is forbidden."
    if filename[:len(beginning)]!=beginning:
        return "Wrong beginning."
    if filename[-len(ending):]!=ending:
        return "Wrong ending."
    try:
        path = Path(filename)
        ret = Response (path.read_bytes(), mimetype="image/png")
        return ret
    except Exception as e:
        print (f"retPNG: {e}", file=sys.stderr)
        return ""

def init (a:Flask, b:str):
    global app
    global baseAddress
    app = a
    baseAddress = b
    app.add_url_rule ("/captcha_html", "getHTML", getHTML)
    app.add_url_rule ("/captcha_png", "retPNG", retPNG)
    print (f"Added all rules to {app}.")

if __name__=="__main__":
    #init (Flask(__name__), "http://127.0.0.1:5000/")
    #app.run("127.0.0.1", port=5000, debug=True)
    capinfo = getCaptcha()
    if capinfo is None:
        print ("capinfo is None.", file=sys.stderr)
        exit()
    fn = capinfo[0]
    solver = Solver (Info(fn))
    solver.start()
    solver.join()
    now=time.time()
    random.seed((now-int(now))*100000000)
    guess = solver.info.description
    print (f"It is a {guess} .")
    print("End.")
