/*
 * This file is part of the Trezor project, https://trezor.io/
 *
 * Copyright (c) SatoshiLabs
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <display.h>

#include "display_draw.h"
#include "fonts/fonts.h"
#include "gfx_draw.h"

typedef struct {
  int16_t dst_x;
  int16_t dst_y;
  int16_t src_x;
  int16_t src_y;
  int16_t width;
  int16_t height;
} gfx_clip_t;

static inline gfx_clip_t gfx_clip(gfx_rect_t dst, const gfx_bitmap_t* bitmap) {
  int16_t dst_x = dst.x0;
  int16_t dst_y = dst.y0;

  int16_t src_x = 0;
  int16_t src_y = 0;

  if (bitmap != NULL) {
    src_x += bitmap->offset.x;
    src_y += bitmap->offset.y;

    // Normalize negative x-offset of bitmap
    if (src_x < 0) {
      dst_x -= src_x;
      src_x = 0;
    }

    // Normalize negative y-offset of src bitmap
    if (src_y < 0) {
      dst_y -= src_y;
      src_y = 0;
    }
  }

  // Normalize negative top-left of destination rectangle
  if (dst_x < 0) {
    src_x -= dst_x;
    dst_x = 0;
  }

  if (dst_y < 0) {
    src_y -= dst_y;
    dst_y = 0;
  }

  // Calculate dimension of effective rectangle
  int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
  int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;

  if (bitmap != NULL) {
    width = MIN(width, bitmap->size.x - src_x);
    height = MIN(height, bitmap->size.y - src_y);
  }

  gfx_clip_t clip = {
      .dst_x = dst_x,
      .dst_y = dst_y,
      .src_x = src_x,
      .src_y = src_y,
      .width = width,
      .height = height,
  };

  return clip;
}

void gfx_clear(void) {
  gfx_bitblt_t bb = {
      // Destination bitmap
      .height = DISPLAY_RESY,
      .width = DISPLAY_RESX,
      .dst_row = NULL,
      .dst_x = 0,
      .dst_y = 0,
      .dst_stride = 0,

      // Source bitmap
      .src_fg = 0,
      .src_alpha = 255,
  };

  display_fill(&bb);
}

void gfx_draw_bar(gfx_rect_t rect, gfx_color_t color) {
  gfx_clip_t clip = gfx_clip(rect, NULL);

  if (clip.width <= 0 || clip.height <= 0) {
    return;
  }

  gfx_bitblt_t bb = {
      // Destination bitmap
      .height = clip.height,
      .width = clip.width,
      .dst_row = NULL,
      .dst_x = clip.dst_x,
      .dst_y = clip.dst_y,
      .dst_stride = 0,

      // Source bitmap
      .src_fg = color,
      .src_alpha = 255,
  };

  display_fill(&bb);
}

void gfx_draw_bitmap(gfx_rect_t rect, const gfx_bitmap_t* bitmap) {
  gfx_clip_t clip = gfx_clip(rect, bitmap);

  if (clip.width <= 0 || clip.height <= 0) {
    return;
  }

  gfx_bitblt_t bb = {
      // Destination bitmap
      .height = clip.height,
      .width = clip.width,
      .dst_row = NULL,
      .dst_x = clip.dst_x,
      .dst_y = clip.dst_y,
      .dst_stride = 0,

      // Source bitmap
      .src_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.src_y,
      .src_x = clip.src_x,
      .src_y = clip.src_y,
      .src_stride = bitmap->stride,
      .src_fg = bitmap->fg_color,
      .src_bg = bitmap->bg_color,
      .src_alpha = 255,
  };

  // Currently, we use `gfx_draw_bitmap` exclusively for text rendering.
  // Therefore, we are including the variant of `display_copy_xxx()` that is
  // specifically needed for drawing glyphs in the format we are using
  // to save some space in the flash memory.

#if TREZOR_FONT_BPP == 1
  if (bitmap->format == GFX_FORMAT_MONO1P) {
    display_copy_mono1p(&bb);
  }
#endif
#if TREZOR_FONT_BPP == 4
  if (bitmap->format == GFX_FORMAT_MONO4) {
    display_copy_mono4(&bb);
  }
#endif
}

#if TREZOR_FONT_BPP == 1
#define GLYPH_FORMAT GFX_FORMAT_MONO1P
#define GLYPH_STRIDE(w) (((w) + 7) / 8)
#elif TREZOR_FONT_BPP == 2
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GFX_FORMAT_MONO2
#define GLYPH_STRIDE(w) (((w) + 3) / 4)
#elif TREZOR_FONT_BPP == 4
#define GLYPH_FORMAT GFX_FORMAT_MONO4
#define GLYPH_STRIDE(w) (((w) + 1) / 2)
#elif TREZOR_FONT_BPP == 8
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GFX_FORMAT_MONO8
#define GLYPH_STRIDE(w) (w)
#else
#error Unsupported TREZOR_FONT_BPP value
#endif

#define GLYPH_WIDTH(g) ((g)[0])
#define GLYPH_HEIGHT(g) ((g)[1])
#define GLYPH_ADVANCE(g) ((g)[2])
#define GLYPH_BEARING_X(g) ((g)[3])
#define GLYPH_BEARING_Y(g) ((g)[4])
#define GLYPH_DATA(g) ((void*)&(g)[5])

void gfx_draw_text(gfx_offset_t pos, const char* text, size_t maxlen,
                   const gfx_text_attr_t* attr) {
  if (text == NULL) {
    return;
  }

  gfx_bitmap_t bitmap = {
      .format = GLYPH_FORMAT,
      .fg_color = attr->fg_color,
      .bg_color = attr->bg_color,
  };

  int max_height = font_max_height(attr->font);
  int baseline = font_baseline(attr->font);

  for (int i = 0; i < maxlen; i++) {
    uint8_t ch = (uint8_t)text[i];

    if (ch == 0 || pos.x >= DISPLAY_RESX) {
      break;
    }

    const uint8_t* glyph = font_get_glyph(attr->font, ch);

    if (glyph == NULL) {
      continue;
    }

    bitmap.ptr = GLYPH_DATA(glyph);
    bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
    bitmap.size.x = GLYPH_WIDTH(glyph);
    bitmap.size.y = GLYPH_HEIGHT(glyph);

    bitmap.offset.x = -GLYPH_BEARING_X(glyph);
    bitmap.offset.y = -(max_height - baseline - GLYPH_BEARING_Y(glyph));

    gfx_draw_bitmap(gfx_rect(pos.x, pos.y, DISPLAY_RESX, DISPLAY_RESY),
                    &bitmap);

    pos.x += GLYPH_ADVANCE(glyph);
  }
}

#ifdef TREZOR_PRODTEST

#include "qr-code-generator/qrcodegen.h"
#define QR_MAX_VERSION 9

void gfx_draw_qrcode(int x, int y, uint8_t scale, const char* data) {
  if (scale < 1 || scale > 10) return;

  uint8_t codedata[qrcodegen_BUFFER_LEN_FOR_VERSION(QR_MAX_VERSION)] = {0};
  uint8_t tempdata[qrcodegen_BUFFER_LEN_FOR_VERSION(QR_MAX_VERSION)] = {0};

  int side = 0;
  if (qrcodegen_encodeText(data, tempdata, codedata, qrcodegen_Ecc_MEDIUM,
                           qrcodegen_VERSION_MIN, QR_MAX_VERSION,
                           qrcodegen_Mask_AUTO, true)) {
    side = qrcodegen_getSize(codedata);
  }

  // Calculate border size (1 extra modules around the QR code)
  int border_side = ((side + 2) * scale);

  // Calculate border left-top corner
  x -= border_side / 2;
  y -= border_side / 2;

  // Fill the backround (including the border) with white color
  gfx_rect_t border_rect = gfx_rect_wh(x, y, border_side, border_side);
  gfx_draw_bar(border_rect, COLOR_WHITE);

  // Center QR code inside the border
  x += scale;
  y += scale;

  // Draw black modules
  for (int i = 0; i < side; i++) {
    for (int j = 0; j < side; j++) {
      if (qrcodegen_getModule(codedata, i, j)) {
        gfx_rect_t rect =
            gfx_rect_wh(x + i * scale, y + j * scale, scale, scale);
        gfx_draw_bar(rect, COLOR_BLACK);
      }
    }
  }
}

#endif  // TREZOR_PRODTEST

// ===============================================================
// emulation of legacy functions

void display_clear(void) { gfx_clear(); }

void display_bar(int x, int y, int w, int h, uint16_t c) {
  gfx_draw_bar(gfx_rect_wh(x, y, w, h), c);
}

void display_text(int x, int y, const char* text, int textlen, int font,
                  uint16_t fg_color, uint16_t bg_color) {
  gfx_text_attr_t attr = {
      .font = font,
      .fg_color = fg_color,
      .bg_color = bg_color,
  };

  size_t maxlen = textlen < 0 ? UINT32_MAX : textlen;
  gfx_draw_text(gfx_offset(x, y), text, maxlen, &attr);
}

void display_text_center(int x, int y, const char* text, int textlen, int font,
                         uint16_t fg_color, uint16_t bg_color) {
  gfx_text_attr_t attr = {
      .font = font,
      .fg_color = fg_color,
      .bg_color = bg_color,
  };

  size_t maxlen = textlen < 0 ? UINT32_MAX : textlen;
  int w = font_text_width(font, text, textlen);
  gfx_draw_text(gfx_offset(x - w / 2, y), text, maxlen, &attr);
}

#ifdef TREZOR_PRODTEST
void display_qrcode(int x, int y, const char* data, uint8_t scale) {
  gfx_draw_qrcode(x, y, scale, data);
}
#endif  // TREZOR_PRODTEST
