import io
import tempfile
import subprocess
from flask import Flask, render_template_string, request, send_file, redirect, url_for, flash
from PIL import Image, ImageDraw, ImageFont
import re

app = Flask(__name__)
app.secret_key = 'TpfxYMxUJxJMtCCTraBSg1rbd6NLz38JTmIfpBLsotcI47EqXU'

FONT_PATH = "/usr/share/fonts/truetype/DejaVuSansMono.ttf"
FONT_PATH_BOLD = "/usr/share/fonts/truetype/DejaVuSansMono-Bold.ttf"
FONT_PATH_OBLIQUE = "/usr/share/fonts/truetype/DejaVuSansMono-Oblique.ttf"
FONT_PATH_BOLDITALIC = "/usr/share/fonts/truetype/DejaVuSansMono-BoldOblique.ttf"
FONT_SIZE = 24
HEADER_SIZE_1 = 56
HEADER_SIZE_2 = 34
HEADER_SIZE_3 = 28
IMAGE_WIDTH = 384
BULLET_CHAR = "• "

HTML_FORM = '''
<!doctype html>
<title>Markdown Thermal Note</title>
<style>
  body {
    font-family: sans-serif;
    background: #181c1f;
    color: #ddd;
    min-height: 100vh;
    margin: 0;
  }
  .centered-flex {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    height: 100vh;
    gap: 2.6em;
    padding-top: 2em;
  }
  .form-card {
    background: #22282c;
    padding: 2em 2.4em 1em 2em;
    border-radius: 1.5em;
    box-shadow: 0 0 12px 0 #000a;
    min-width: 410px;
    max-width: 450px;
    margin-right: 0;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
  }
  textarea {
    width: 100%;
    height: 14em;
    padding: 0.75em;
    border-radius: 0.7em;
    border: 1.5px solid #444;
    background: #181c1f;
    color: #eee;
    font-size: 1.06em;
    margin-bottom: 1.5em;
    resize: vertical;
    font-family: 'DejaVu Sans Mono', 'Fira Mono', 'monospace';
    box-sizing: border-box;
    margin-right: 0;
    min-width: 0;
  }
  .buttons {
    display: flex;
    gap: 1em;
    align-items: center;
  }
  button[type=submit] {
    background: linear-gradient(90deg, #8ee3c1, #35a7ff 85%);
    color: #222;
    font-weight: bold;
    font-size: 1.12em;
    border: none;
    border-radius: 0.7em;
    padding: 0.5em 1.6em;
    margin-top: 0.1em;
    box-shadow: 0 2px 12px #1116;
    cursor: pointer;
    transition: filter 0.2s, box-shadow 0.2s;
    filter: brightness(1);
  }
  button[type=submit]:hover {
    filter: brightness(1.12);
    box-shadow: 0 4px 18px #2229;
  }
  .preview-card {
    background: #23282d;
    padding: 2em 1.4em 1em 1.4em;
    border-radius: 1.5em;
    box-shadow: 0 0 12px 0 #0007;
    min-width: 400px;
    max-width: 400px;
    text-align: center;
    box-sizing: border-box;
    margin-left: 0;
  }
  img {
    margin-top: 1em;
    max-width: 100%;
    background: #fff;
    border-radius: 0.6em;
    box-shadow: 0 0 8px 1px #111a;
  }
  .status-msg {
    margin: 0.6em 0 0.1em 0;
    font-size: 1.12em;
    border-radius: 0.7em;
    background: #294b3a;
    color: #9fffc6;
    padding: 0.55em 1.2em;
    font-weight: 600;
    box-shadow: 0 0 6px #183a24a0;
    display: inline-block;
    border-left: 4px solid #4ffab0;
  }
  .status-err {
    background: #48202c;
    color: #ffdbe4;
    border-left: 4px solid #ff6384;
    box-shadow: 0 0 8px #22000690;
  }
</style>
<div class="centered-flex">
  <div class="form-card">
    <h2 style="margin-top:0.2em;">CatNotepad</h2>
    <form method=post enctype=multipart/form-data>
      <textarea name=md placeholder="Ingrese Markdown aquí...">{{ default_md }}</textarea><br>
      <div class="buttons">
        <button type=submit name="generate">📷 Generar</button>
        <button type=submit name="print" style="background:linear-gradient(90deg,#ffeb3b 55%,#ff9100 95%);color:#181c1f;">🖨️ Imprimir</button>
      </div>
    </form>
    {% if printed %}
      <div class="status-msg">✅ Enviado a impresora</div>
    {% endif %}
    {% if error %}
      <div class="status-msg status-err">⚠️ {{ error }}</div>
    {% endif %}
  </div>
  <div class="preview-card">
    {% if img %}
      <h3 style="margin-top:0.1em;margin-bottom:0.6em;">Vista previa</h3>
      <img src="data:image/png;base64,{{ img }}">
    {% else %}
      <p style="opacity:.6;">Su vista previa aparecerá aquí</p>
    {% endif %}
  </div>
</div>
'''

def parse_line(line):
    header_level = 0
    header_match = re.match(r"^(#{1,3}) +(.*)", line)
    if header_match:
        header_level = len(header_match.group(1))
        line = header_match.group(2)
        return ('header', header_level, parse_segments(line))
    bullet_match = re.match(r"^\s*([-*\u2022]) +(.*)", line)
    if bullet_match:
        return ('bullet', parse_segments(bullet_match.group(2)))
    ordered_match = re.match(r"^\s*(\d+)\. +(.*)", line)
    if ordered_match:
        return ('ordered', int(ordered_match.group(1)), parse_segments(ordered_match.group(2)))
    return ('text', parse_segments(line))

def parse_segments(line):
    bi = re.findall(r"\*\*\*(.+?)\*\*\*", line)
    for x in bi:
        line = line.replace(f"***{x}***", f"\x01{x}\x02")
    b = re.findall(r"\*\*(.+?)\*\*", line)
    for x in b:
        line = line.replace(f"**{x}**", f"\x03{x}\x04")
    i = re.findall(r"\*(.+?)\*", line)
    for x in i:
        line = line.replace(f"*{x}*", f"\x05{x}\x06")
    segments = []
    i = 0
    style = 'text'
    while i < len(line):
        if line[i] == '\x01':
            style = 'bolditalic'
            i += 1
            start = i
            while i < len(line) and line[i] != '\x02': i += 1
            segments.append(('bolditalic', line[start:i]))
            style = 'text'
            i += 1
        elif line[i] == '\x03':
            style = 'bold'
            i += 1
            start = i
            while i < len(line) and line[i] != '\x04': i += 1
            segments.append(('bold', line[start:i]))
            style = 'text'
            i += 1
        elif line[i] == '\x05':
            style = 'italic'
            i += 1
            start = i
            while i < len(line) and line[i] != '\x06': i += 1
            segments.append(('italic', line[start:i]))
            style = 'text'
            i += 1
        else:
            start = i
            while i < len(line) and line[i] not in '\x01\x03\x05': i += 1
            if i > start:
                segments.append(('text', line[start:i]))
    return segments

def font_for_style(style, font, font_bold, font_italic, font_bolditalic):
    if style == 'bolditalic':
        return font_bolditalic or font_bold or font_italic or font
    elif style == 'bold':
        return font_bold
    elif style == 'italic':
        return font_italic
    else:
        return font

def wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, max_width, start_x=0):
    line = []
    x = start_x
    for style, text in segments:
        words = re.split(r'(\s+)', text)
        for word in words:
            if word == '':
                continue
            f = font_for_style(style, font, font_bold, font_italic, font_bolditalic)
            w = f.getbbox(word)[2] - f.getbbox(word)[0]
            if x + w > max_width and line:
                yield line
                line = []
                x = start_x
            line.append((style, word))
            x += w
    if line:
        yield line

def render(md):
    font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
    font_bold = ImageFont.truetype(FONT_PATH_BOLD, FONT_SIZE)
    font_italic = ImageFont.truetype(FONT_PATH_OBLIQUE, FONT_SIZE)
    try:
        font_bolditalic = ImageFont.truetype(FONT_PATH_BOLDITALIC, FONT_SIZE)
    except:
        font_bolditalic = None
    font_h1 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_1)
    font_h2 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_2)
    font_h3 = ImageFont.truetype(FONT_PATH_BOLD, HEADER_SIZE_3)
    lines_out = []
    for src_line in md.splitlines():
        if src_line.strip() == '':
            lines_out.append(('blank', []))
            continue
        tag = parse_line(src_line)
        if tag[0] == 'header':
            header_level = tag[1]
            segments = tag[2]
            if header_level == 1:
                font_h = font_h1
                size_h = HEADER_SIZE_1
            elif header_level == 2:
                font_h = font_h2
                size_h = HEADER_SIZE_2
            else:
                font_h = font_h3
                size_h = HEADER_SIZE_3
            for wrapped in wrap_segments(segments, font_h, font_h, font_h, font_h, IMAGE_WIDTH):
                lines_out.append(('header', wrapped, font_h, size_h))
        elif tag[0] == 'bullet':
            segments = tag[1]
            bullet_font = font
            bullet_w = bullet_font.getbbox(BULLET_CHAR)[2] - bullet_font.getbbox(BULLET_CHAR)[0]
            wrapped_lines = list(wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, IMAGE_WIDTH - bullet_w, start_x=0))
            for i, wrapped in enumerate(wrapped_lines):
                if i == 0:
                    lines_out.append(('bullet', wrapped, bullet_font, FONT_SIZE, True, bullet_w))
                else:
                    lines_out.append(('bullet', wrapped, bullet_font, FONT_SIZE, False, bullet_w))
        elif tag[0] == 'ordered':
            idx, segments = tag[1], tag[2]
            num_str = f"{idx}. "
            number_font = font
            num_w = number_font.getbbox(num_str)[2] - number_font.getbbox(num_str)[0]
            wrapped_lines = list(wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, IMAGE_WIDTH - num_w, start_x=0))
            for i, wrapped in enumerate(wrapped_lines):
                if i == 0:
                    lines_out.append(('ordered', wrapped, number_font, FONT_SIZE, num_str, True, num_w))
                else:
                    lines_out.append(('ordered', wrapped, number_font, FONT_SIZE, num_str, False, num_w))
        else:  # normal text
            segments = tag[1]
            for wrapped in wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, IMAGE_WIDTH):
                lines_out.append(('text', wrapped, font, FONT_SIZE))
    height = sum(item[3] if item[0] in ('header','text','bullet','ordered') else FONT_SIZE for item in lines_out) + 10
    image = Image.new("L", (IMAGE_WIDTH, height), 255)
    draw = ImageDraw.Draw(image)
    y = 0
    for item in lines_out:
        if item[0] == 'blank':
            y += FONT_SIZE
        elif item[0] == 'header':
            segments, fnt, sz = item[1], item[2], item[3]
            x = 0
            for style, text in segments:
                draw.text((x, y), text, font=fnt, fill=0)
                x += fnt.getbbox(text)[2] - fnt.getbbox(text)[0]
            y += sz
        elif item[0] == 'bullet':
            segments, bullet_font, sz, show_bullet, bullet_w = item[1], item[2], item[3], item[4], item[5]
            x = 0
            if show_bullet:
                draw.text((x, y), BULLET_CHAR, font=bullet_font, fill=0)
                x += bullet_w
            else:
                x += bullet_w
            for style, text in segments:
                f = font_for_style(style, bullet_font, font_bold, font_italic, font_bolditalic)
                draw.text((x, y), text, font=f, fill=0)
                x += f.getbbox(text)[2] - f.getbbox(text)[0]
            y += sz
        elif item[0] == 'ordered':
            segments, number_font, sz, num_str, show_num, num_w = item[1], item[2], item[3], item[4], item[5], item[6]
            x = 0
            if show_num:
                draw.text((x, y), num_str, font=number_font, fill=0)
                x += num_w
            else:
                x += num_w
            for style, text in segments:
                f = font_for_style(style, number_font, font_bold, font_italic, font_bolditalic)
                draw.text((x, y), text, font=f, fill=0)
                x += f.getbbox(text)[2] - f.getbbox(text)[0]
            y += sz
        elif item[0] == 'text':
            segments, fnt, sz = item[1], item[2], item[3]
            x = 0
            for style, text in segments:
                f = font_for_style(style, font, font_bold, font_italic, font_bolditalic)
                draw.text((x, y), text, font=f, fill=0)
                x += f.getbbox(text)[2] - f.getbbox(text)[0]
            y += sz
    return image


@app.route("/", methods=["GET", "POST"])
def index():
    img_data = None
    md = ""
    printed = False
    error = None
    if request.method == "POST":
        md = request.form["md"]
        image = render(md)
        buf = io.BytesIO()
        image.save(buf, format="PNG")
        buf.seek(0)
        import base64
        img_data = base64.b64encode(buf.getvalue()).decode()
        # If print button pressed, send to catprinter-ble
        if "print" in request.form:
            try:
                with tempfile.NamedTemporaryFile(suffix=".png", delete=True) as tmpfile:
                    image.save(tmpfile, format="PNG")
                    tmpfile.flush()
                    # Run the catprinter-ble command
                    # You can set dither method here if desired
                    result = subprocess.run([
                        "sudo",
                        "./catprinter-ble",
                        "-mode", "1bpp",
                        "-intensity", "100",
                        tmpfile.name
                    ], capture_output=True, text=True, timeout=90)
                    if result.returncode != 0:
                        error = f"Printer error: {result.stderr or result.stdout}"
                    else:
                        printed = True
            except Exception as e:
                error = f"Failed to print: {e}"
    return render_template_string(HTML_FORM, img=img_data, default_md=md,
        printed=printed, error=error)

if __name__ == "__main__":
    app.run(host="0.0.0.0")
