/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtypes.h"

#include "gcontext.h"
#include "process_management/process_manager.h"
#include "process_state/app_state/app_state.h"
#include "system/passert.h"
#include "util/math.h"

#include <stddef.h>

bool gpoint_equal(const GPoint * const point_a, const GPoint * const point_b) {
  return (point_a->x == point_b->x && point_a->y == point_b->y);
}

static void prv_swap_gpoint(GPoint *a, GPoint *b) {
  GPoint t = *a;
  *a = *b;
  *b = t;
}

void gpoint_sort(GPoint *points, size_t num_points, GPointComparator comparator, void *context,
    bool reverse) {
  for (size_t i = 0; i < num_points; i++) {
    for (size_t j = i + 1; j < num_points; j++) {
      int cmp = comparator(&points[i], &points[j], context);
      if (reverse ? cmp < 0 : cmp > 0) {
        prv_swap_gpoint(&points[i], &points[j]);
      }
    }
  }
}

bool gpointprecise_equal(const GPointPrecise * const pointP_a,
                         const GPointPrecise * const pointP_b) {
  return ((pointP_a->x.raw_value == pointP_b->x.raw_value) &&
          (pointP_a->y.raw_value == pointP_b->y.raw_value));
}

GPointPrecise gpointprecise_midpoint(const GPointPrecise a,
                                     const GPointPrecise b) {
  return GPointPrecise(
    (int16_t)(((int32_t)a.x.raw_value + (int32_t)b.x.raw_value) / 2),
    (int16_t)(((int32_t)a.y.raw_value + (int32_t)b.y.raw_value) / 2));
}

GPointPrecise gpointprecise_add(const GPointPrecise a,
                                const GPointPrecise b) {
  return GPointPrecise(a.x.raw_value + b.x.raw_value,
                       a.y.raw_value + b.y.raw_value);
}

GPointPrecise gpointprecise_sub(const GPointPrecise a,
                                const GPointPrecise b) {
  return GPointPrecise(a.x.raw_value - b.x.raw_value,
                       a.y.raw_value - b.y.raw_value);
}


bool gvector_equal(const GVector * const vector_a, const GVector * const vector_b) {
  return (vector_a->dx == vector_b->dx && vector_a->dy == vector_b->dy);
}

bool gvectorprecise_equal(const GVectorPrecise * const vectorP_a,
                          const GVectorPrecise * const vectorP_b) {
  return ((vectorP_a->dx.raw_value == vectorP_b->dx.raw_value) &&
          (vectorP_a->dy.raw_value == vectorP_b->dy.raw_value));
}

bool gsize_equal(const GSize *size_a, const GSize *size_b) {
  return (size_a->w == size_b->w && size_a->h == size_b->h);
}

bool grect_equal(const GRect* const r0, const GRect* const r1) {
  return ((r0->origin.x == r1->origin.x) &&
          (r0->origin.y == r1->origin.y) &&
          (r0->size.w == r1->size.w) &&
          (r0->size.h == r1->size.h));
}

bool grect_is_empty(const GRect* const rect) {
  return (rect->size.h == 0 || rect->size.w == 0);
}

void grect_standardize(GRect *rect) {
  if (rect->size.w < 0) {
    rect->origin.x += rect->size.w;
    rect->size.w = -rect->size.w;
  }
  if (rect->size.h < 0) {
    rect->origin.y += rect->size.h;
    rect->size.h = -rect->size.h;
  }
}

void grect_clip(GRect *rect_to_clip, const GRect * const rect_clipper) {
  int16_t overflow;
  if (rect_to_clip->origin.x < rect_clipper->origin.x) {
    overflow = rect_clipper->origin.x - rect_to_clip->origin.x;
    if (overflow > rect_to_clip->size.w) {
      rect_to_clip->size.w = 0;
    } else {
      rect_to_clip->size.w -= overflow;
    }
    rect_to_clip->origin.x = rect_clipper->origin.x;
  } else if (rect_to_clip->origin.x > rect_clipper->origin.x + rect_clipper->size.w) {
    rect_to_clip->origin.x = rect_clipper->origin.x + rect_clipper->size.w;
    rect_to_clip->size.w = 0;
  }
  overflow = rect_to_clip->origin.x + rect_to_clip->size.w - (rect_clipper->origin.x + rect_clipper->size.w);
  if (overflow > 0) {
    rect_to_clip->size.w -= overflow;
  }
  if (rect_to_clip->origin.y < rect_clipper->origin.y) {
    overflow = rect_clipper->origin.y - rect_to_clip->origin.y;
    if (overflow > rect_to_clip->size.h) {
      rect_to_clip->size.h = 0;
    } else {
      rect_to_clip->size.h -= overflow;
    }
    rect_to_clip->origin.y = rect_clipper->origin.y;
  } else if (rect_to_clip->origin.y > rect_clipper->origin.y + rect_clipper->size.h) {
    rect_to_clip->origin.y = rect_clipper->origin.y + rect_clipper->size.h;
    rect_to_clip->size.h = 0;
  }
  overflow = rect_to_clip->origin.y + rect_to_clip->size.h - (rect_clipper->origin.y + rect_clipper->size.h);
  if (overflow > 0) {
    rect_to_clip->size.h -= overflow;
  }
}

GRect grect_union(const GRect *r1, const GRect *r2) {
  GRect s_r1 = *r1;
  GRect s_r2 = *r2;
  grect_standardize(&s_r1);
  grect_standardize(&s_r2);
  const uint8_t min_x = MIN(s_r2.origin.x, s_r1.origin.x);
  const uint8_t min_y = MIN(s_r2.origin.y, s_r1.origin.y);
  const uint8_t max_x = MAX(s_r2.origin.x + s_r2.size.w,
                            s_r1.origin.x + s_r1.size.w);
  const uint8_t max_y = MAX(s_r2.origin.y + s_r2.size.h,
                            s_r1.origin.y + s_r1.size.h);
  GRect result = GRect(min_x, min_y, max_x - min_x, max_y - min_y);
  return result;
}

GPoint grect_center_point(const GRect *rect) {
  return GPoint(rect->origin.x + (rect->size.w / 2), rect->origin.y + (rect->size.h / 2));
}

bool grect_contains_point(const GRect *rect, const GPoint *point) {
  int16_t min_x = rect->origin.x;
  int16_t max_x = rect->origin.x + rect->size.w;
  if (min_x > max_x) {
    // edge case for non-standardized rects:
    int16_t temp = max_x;
    max_x = min_x;
    min_x = temp;
  }
  int16_t min_y = rect->origin.y;
  int16_t max_y = rect->origin.y + rect->size.h;
  if (min_y > max_y) {
    // edge case for non-standardized rects:
    int16_t temp = max_y;
    max_y = min_y;
    min_y = temp;
  }
  return (point->x >= min_x && point->x < max_x &&
          point->y >= min_y && point->y < max_y);
}

void grect_align(GRect *rect, const GRect *inside_rect, const GAlign alignment, const bool clip) {
  if (clip) {
    if (rect->size.w > inside_rect->size.w) {
      rect->size.w = inside_rect->size.w;
    }
    if (rect->size.h > inside_rect->size.h) {
      rect->size.h = inside_rect->size.h;
    }
  }

  switch (alignment) {
    case GAlignCenter: {
      rect->origin.x = ((inside_rect->size.w - rect->size.w) / 2) + inside_rect->origin.x;
      rect->origin.y = ((inside_rect->size.h - rect->size.h) / 2) + inside_rect->origin.y;
      return;
    }
    case GAlignTopLeft: {
      rect->origin.x = inside_rect->origin.x;
      rect->origin.y = + inside_rect->origin.y;
      return;
    }
    case GAlignTopRight: {
      rect->origin.x = (inside_rect->size.w - rect->size.w) + inside_rect->origin.x;
      rect->origin.y = + inside_rect->origin.y;
      return;
    }
    case GAlignTop: {
      rect->origin.x = ((inside_rect->size.w - rect->size.w) / 2) + inside_rect->origin.x;
      rect->origin.y = + inside_rect->origin.y;
      return;
    }
    case GAlignLeft: {
      rect->origin.x = inside_rect->origin.x;
      rect->origin.y = ((inside_rect->size.h - rect->size.h) / 2) + inside_rect->origin.y;
      return;
    }
    case GAlignBottom: {
      rect->origin.x = ((inside_rect->size.w - rect->size.w) / 2) + inside_rect->origin.x;
      rect->origin.y = (inside_rect->size.h - rect->size.h) + inside_rect->origin.y;
      return;
    }
    case GAlignRight: {
      rect->origin.x = (inside_rect->size.w - rect->size.w) + inside_rect->origin.x;
      rect->origin.y = ((inside_rect->size.h - rect->size.h) / 2) + inside_rect->origin.y;
      return;
    }
    case GAlignBottomRight: {
      rect->origin.x = (inside_rect->size.w - rect->size.w) + inside_rect->origin.x;
      rect->origin.y = (inside_rect->size.h - rect->size.h) + inside_rect->origin.y;
      return;
    }
    case GAlignBottomLeft: {
      rect->origin.x = inside_rect->origin.x;
      rect->origin.y = (inside_rect->size.h - rect->size.h) + inside_rect->origin.y;
      return;
    }
  }
}

GRect grect_crop(GRect rect, const int32_t crop_size_px) {
  int16_t cropped_width = rect.size.w - 2 * crop_size_px;
  int16_t cropped_height = rect.size.h - 2 * crop_size_px;

  PBL_ASSERTN(cropped_width >= 0);
  PBL_ASSERTN(cropped_height >= 0);

  return grect_inset_internal(rect, crop_size_px, crop_size_px);
}

GRect grect_inset_internal(GRect rect, int16_t dx, int16_t dy) {
  return grect_inset(rect,
                     (GEdgeInsets) {.top = dy, .right = dx, .bottom = dy, .left = dx});
}

GRect grect_inset(GRect r, GEdgeInsets insets) {
  grect_standardize(&r);
  const int16_t new_width = r.size.w - insets.left - insets.right;
  const int16_t new_height = r.size.h - insets.top - insets.bottom;
  if (new_width < 0 || new_height < 0) {
    return GRectZero;
  }
  return GRect(r.origin.x + insets.left, r.origin.y + insets.top, new_width, new_height);
}

GPoint gpoint_to_global_coordinates(const GPoint point, GContext *ctx) {
  return gpoint_add(point, ctx->draw_state.drawing_box.origin);
}

GPoint gpoint_to_local_coordinates(const GPoint point, GContext *ctx) {
  return gpoint_sub(point, ctx->draw_state.drawing_box.origin);
}

GRect grect_to_global_coordinates(const GRect rect, GContext *ctx) {
  GRect translated_rect = {
    .origin.x = ctx->draw_state.drawing_box.origin.x + rect.origin.x,
    .origin.y = ctx->draw_state.drawing_box.origin.y + rect.origin.y,
    .size = rect.size,
  };
  return translated_rect;
}

GRect grect_to_local_coordinates(const GRect rect, GContext *ctx) {
  GRect translated_rect = {
    .origin.x = -ctx->draw_state.drawing_box.origin.x + rect.origin.x,
    .origin.y = -ctx->draw_state.drawing_box.origin.y + rect.origin.y,
    .size = rect.size,
  };
  return translated_rect;
}

bool grect_overlaps_grect(const GRect *r1, const GRect *r2) {
  if ((r1->origin.x < (r2->origin.x + r2->size.w)) && // Left edge of r1 not past r2's right edge
      ((r1->origin.x + r1->size.w) > r2->origin.x) && // Right edge of r1 is right of r2's left edge
      (r1->origin.y < (r2->origin.y + r2->size.h)) && // Top edge r1 not below r2's bottom edge
      ((r1->origin.y + r1->size.h) > r2->origin.y)) { // Bottom edge r1 not above r2's top edge
    return true;
  }
  return false;
}

void grect_precise_standardize(GRectPrecise *rect) {
  if (rect->size.w.raw_value < 0) {
    rect->origin.x.raw_value += rect->size.w.raw_value;
    rect->size.w.raw_value = -rect->size.w.raw_value;
  }
  if (rect->size.h.raw_value < 0) {
    rect->origin.y.raw_value += rect->size.h.raw_value;
    rect->size.h.raw_value = -rect->size.h.raw_value;
  }
}

bool gcolor_is_transparent(GColor8 color) {
  // Mimic a "closest color" behaviour, since we do not have blending.
  return (color.a <= 1);
}

GColor8 gcolor_closest_opaque(GColor8 color) {
  if (gcolor_is_transparent(color)) {
    return GColorClear;
  }
  color.a = 3;
  return color;
}

static int32_t prv_get_luminance_10000(GColor8 color) {
  // fixed-point implementation (to base 10,000 decimal) of
  // luminance = (0.2126*R + 0.7152*G + 0.0722*B)
  // as in http://stackoverflow.com/a/596243
  return (2126 * color.r + 7152 * color.g + 722 * color.b) / 3;
}

GColor8 gcolor_get_bw(GColor8 color) {
  if (gcolor_is_transparent(color)) {
    return GColorClear;
  }

  const int32_t MAX_LUMINANCE = 10000;
  const int32_t luminance = prv_get_luminance_10000(color);

  if (luminance < MAX_LUMINANCE / 2) {
    return GColorBlack;
  }
  else {
    return GColorWhite;
  }
}

GColor8 gcolor_get_grayscale(GColor8 color) {
  if (gcolor_is_transparent(color)) {
    return GColorClear;
  }

  const int32_t DARK_GRAY_LUMINANCE = 3333;
  const int32_t LIGHT_GRAY_LUMINANCE = 6666;
  const int32_t luminance = prv_get_luminance_10000(color);

  if (luminance < DARK_GRAY_LUMINANCE) {
    return GColorBlack;
  }
  else if (luminance < (LIGHT_GRAY_LUMINANCE + DARK_GRAY_LUMINANCE) / 2) {
    return GColorDarkGray;
  }
  else if (luminance <= LIGHT_GRAY_LUMINANCE) {
    return GColorLightGray;
  }
  else {
    return GColorWhite;
  }
}

GColor8 gcolor_legible_over(GColor8 background_color) {
  background_color = gcolor_closest_opaque(background_color);

  // special cases - needed to fulfill test_graphics_colors__inverted_readable_color()
  switch (background_color.argb) {
    case GColorClearARGB8:
      return GColorClear;
  }

  const int32_t luminance = prv_get_luminance_10000(background_color);

  // this value is derived from test_graphics_colors__inverted_readable_color()
  const int32_t MAGIC_THRESHOLD = 4510;
  const bool bright = luminance >= MAGIC_THRESHOLD;

  return bright ? GColorBlack : GColorWhite;
}

BitmapInfo gbitmap_get_info(const GBitmap *bitmap) {
  if (!bitmap) {
    return (BitmapInfo) { 0 };
  }

  // In 2.x, GBitmap was exposed and info_flags was only used for keeping track of heap allocation.
  // Some apps were constructing their own GBitmaps, and not setting info_flags.
  // For a legacy2 app, zero out all info fields except for bitmap heap allocation, and assume
  // that if the flag is set and the bitmap is allocated on the heap, the flag is valid.
  if (process_manager_compiled_with_legacy2_sdk()) {
    Heap * heap = app_state_get_heap();
    return (BitmapInfo) {
      .is_bitmap_heap_allocated =
          bitmap->info.is_bitmap_heap_allocated && heap_is_allocated(heap, bitmap->addr),
    };
  }
  return bitmap->info;
}

bool gcolor_is_invisible(GColor8 color) {
  return (color.a == 0);
}

#define RGB_LOOKUP_TABLE_SIZE (64)

const GColor8Component g_color_luminance_lookup[RGB_LOOKUP_TABLE_SIZE] = {
  0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
  0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
  1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
};

// Table with blended colors rendered by Photoshop
// 64 rows for 64 source colors and 64 columns for 64 destination colors
// Values below were calculated for 33% blending
// 66% blending can be achieved by transforming the table around diagonal axis
static const uint8_t s_blending_lookup_33_percent[RGB_LOOKUP_TABLE_SIZE * RGB_LOOKUP_TABLE_SIZE] = {
  0xc0, 0xc1, 0xc1, 0xc2, 0xc4, 0xc5, 0xc5, 0xc6, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xc0, 0xc1, 0xc2, 0xc2, 0xc4, 0xc5, 0xc6, 0xc6, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xc1, 0xc1, 0xc2, 0xc3, 0xc5, 0xc5, 0xc6, 0xc7, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xc1, 0xc2, 0xc2, 0xc3, 0xc5, 0xc6, 0xc6, 0xc7, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xc0, 0xc1, 0xc1, 0xc2, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xc8, 0xc9, 0xc9, 0xca,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xc0, 0xc1, 0xc2, 0xc2, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xc8, 0xc9, 0xca, 0xca,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xc1, 0xc1, 0xc2, 0xc3, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xc9, 0xc9, 0xca, 0xcb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xc1, 0xc2, 0xc2, 0xc3, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xc9, 0xca, 0xca, 0xcb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xc4, 0xc5, 0xc5, 0xc6, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xcc, 0xcd, 0xcd, 0xce,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xc4, 0xc5, 0xc6, 0xc6, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xcc, 0xcd, 0xce, 0xce,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xc5, 0xc5, 0xc6, 0xc7, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xcd, 0xcd, 0xce, 0xcf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xc5, 0xc6, 0xc6, 0xc7, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xcd, 0xce, 0xce, 0xcf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xc8, 0xc9, 0xc9, 0xca, 0xcc, 0xcd, 0xcd, 0xce,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xc8, 0xc9, 0xca, 0xca, 0xcc, 0xcd, 0xce, 0xce,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xc9, 0xc9, 0xca, 0xcb, 0xcd, 0xcd, 0xce, 0xcf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xc9, 0xca, 0xca, 0xcb, 0xcd, 0xce, 0xce, 0xcf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xc0, 0xc1, 0xc1, 0xc2, 0xc4, 0xc5, 0xc5, 0xc6, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xc0, 0xc1, 0xc2, 0xc2, 0xc4, 0xc5, 0xc6, 0xc6, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xc1, 0xc1, 0xc2, 0xc3, 0xc5, 0xc5, 0xc6, 0xc7, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xc1, 0xc2, 0xc2, 0xc3, 0xc5, 0xc6, 0xc6, 0xc7, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xc0, 0xc1, 0xc1, 0xc2, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xc8, 0xc9, 0xc9, 0xca,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xc0, 0xc1, 0xc2, 0xc2, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xc8, 0xc9, 0xca, 0xca,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xc1, 0xc1, 0xc2, 0xc3, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xc9, 0xc9, 0xca, 0xcb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xc1, 0xc2, 0xc2, 0xc3, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xc9, 0xca, 0xca, 0xcb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xc4, 0xc5, 0xc5, 0xc6, 0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xcc, 0xcd, 0xcd, 0xce,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xc4, 0xc5, 0xc6, 0xc6, 0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xcc, 0xcd, 0xce, 0xce,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xc5, 0xc5, 0xc6, 0xc7, 0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xcd, 0xcd, 0xce, 0xcf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xc5, 0xc6, 0xc6, 0xc7, 0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xcd, 0xce, 0xce, 0xcf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xc4, 0xc5, 0xc5, 0xc6, 0xc8, 0xc9, 0xc9, 0xca, 0xc8, 0xc9, 0xc9, 0xca, 0xcc, 0xcd, 0xcd, 0xce,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xc4, 0xc5, 0xc6, 0xc6, 0xc8, 0xc9, 0xca, 0xca, 0xc8, 0xc9, 0xca, 0xca, 0xcc, 0xcd, 0xce, 0xce,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xc5, 0xc5, 0xc6, 0xc7, 0xc9, 0xc9, 0xca, 0xcb, 0xc9, 0xc9, 0xca, 0xcb, 0xcd, 0xcd, 0xce, 0xcf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xc5, 0xc6, 0xc6, 0xc7, 0xc9, 0xca, 0xca, 0xcb, 0xc9, 0xca, 0xca, 0xcb, 0xcd, 0xce, 0xce, 0xcf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xf0, 0xf1, 0xf1, 0xf2, 0xf4, 0xf5, 0xf5, 0xf6, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xf0, 0xf1, 0xf2, 0xf2, 0xf4, 0xf5, 0xf6, 0xf6, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xf1, 0xf1, 0xf2, 0xf3, 0xf5, 0xf5, 0xf6, 0xf7, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xf1, 0xf2, 0xf2, 0xf3, 0xf5, 0xf6, 0xf6, 0xf7, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xf0, 0xf1, 0xf1, 0xf2, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xf8, 0xf9, 0xf9, 0xfa,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xf0, 0xf1, 0xf2, 0xf2, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xf8, 0xf9, 0xfa, 0xfa,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xf1, 0xf1, 0xf2, 0xf3, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xf9, 0xf9, 0xfa, 0xfb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xf1, 0xf2, 0xf2, 0xf3, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xf9, 0xfa, 0xfa, 0xfb,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xf4, 0xf5, 0xf5, 0xf6, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xfc, 0xfd, 0xfd, 0xfe,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xf4, 0xf5, 0xf6, 0xf6, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xfc, 0xfd, 0xfe, 0xfe,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xf5, 0xf5, 0xf6, 0xf7, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xfd, 0xfd, 0xfe, 0xff,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xf5, 0xf6, 0xf6, 0xf7, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xfd, 0xfe, 0xfe, 0xff,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xf8, 0xf9, 0xf9, 0xfa, 0xfc, 0xfd, 0xfd, 0xfe,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xf8, 0xf9, 0xfa, 0xfa, 0xfc, 0xfd, 0xfe, 0xfe,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xf9, 0xf9, 0xfa, 0xfb, 0xfd, 0xfd, 0xfe, 0xff,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xf9, 0xfa, 0xfa, 0xfb, 0xfd, 0xfe, 0xfe, 0xff,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea,
  0xf0, 0xf1, 0xf1, 0xf2, 0xf4, 0xf5, 0xf5, 0xf6, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea,
  0xf0, 0xf1, 0xf2, 0xf2, 0xf4, 0xf5, 0xf6, 0xf6, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb,
  0xf1, 0xf1, 0xf2, 0xf3, 0xf5, 0xf5, 0xf6, 0xf7, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb,
  0xf1, 0xf2, 0xf2, 0xf3, 0xf5, 0xf6, 0xf6, 0xf7, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb,
  0xd0, 0xd1, 0xd1, 0xd2, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xe0, 0xe1, 0xe1, 0xe2, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea,
  0xf0, 0xf1, 0xf1, 0xf2, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xf8, 0xf9, 0xf9, 0xfa,
  0xd0, 0xd1, 0xd2, 0xd2, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xe0, 0xe1, 0xe2, 0xe2, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea,
  0xf0, 0xf1, 0xf2, 0xf2, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xf8, 0xf9, 0xfa, 0xfa,
  0xd1, 0xd1, 0xd2, 0xd3, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xe1, 0xe1, 0xe2, 0xe3, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb,
  0xf1, 0xf1, 0xf2, 0xf3, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xf9, 0xf9, 0xfa, 0xfb,
  0xd1, 0xd2, 0xd2, 0xd3, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xe1, 0xe2, 0xe2, 0xe3, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb,
  0xf1, 0xf2, 0xf2, 0xf3, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xf9, 0xfa, 0xfa, 0xfb,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xf4, 0xf5, 0xf5, 0xf6, 0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xfc, 0xfd, 0xfd, 0xfe,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xf4, 0xf5, 0xf6, 0xf6, 0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xfc, 0xfd, 0xfe, 0xfe,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xf5, 0xf5, 0xf6, 0xf7, 0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xfd, 0xfd, 0xfe, 0xff,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xf5, 0xf6, 0xf6, 0xf7, 0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xfd, 0xfe, 0xfe, 0xff,
  0xd4, 0xd5, 0xd5, 0xd6, 0xd8, 0xd9, 0xd9, 0xda, 0xd8, 0xd9, 0xd9, 0xda, 0xdc, 0xdd, 0xdd, 0xde,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xe4, 0xe5, 0xe5, 0xe6, 0xe8, 0xe9, 0xe9, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xec, 0xed, 0xed, 0xee,
  0xf4, 0xf5, 0xf5, 0xf6, 0xf8, 0xf9, 0xf9, 0xfa, 0xf8, 0xf9, 0xf9, 0xfa, 0xfc, 0xfd, 0xfd, 0xfe,
  0xd4, 0xd5, 0xd6, 0xd6, 0xd8, 0xd9, 0xda, 0xda, 0xd8, 0xd9, 0xda, 0xda, 0xdc, 0xdd, 0xde, 0xde,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xe4, 0xe5, 0xe6, 0xe6, 0xe8, 0xe9, 0xea, 0xea, 0xe8, 0xe9, 0xea, 0xea, 0xec, 0xed, 0xee, 0xee,
  0xf4, 0xf5, 0xf6, 0xf6, 0xf8, 0xf9, 0xfa, 0xfa, 0xf8, 0xf9, 0xfa, 0xfa, 0xfc, 0xfd, 0xfe, 0xfe,
  0xd5, 0xd5, 0xd6, 0xd7, 0xd9, 0xd9, 0xda, 0xdb, 0xd9, 0xd9, 0xda, 0xdb, 0xdd, 0xdd, 0xde, 0xdf,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xe5, 0xe5, 0xe6, 0xe7, 0xe9, 0xe9, 0xea, 0xeb, 0xe9, 0xe9, 0xea, 0xeb, 0xed, 0xed, 0xee, 0xef,
  0xf5, 0xf5, 0xf6, 0xf7, 0xf9, 0xf9, 0xfa, 0xfb, 0xf9, 0xf9, 0xfa, 0xfb, 0xfd, 0xfd, 0xfe, 0xff,
  0xd5, 0xd6, 0xd6, 0xd7, 0xd9, 0xda, 0xda, 0xdb, 0xd9, 0xda, 0xda, 0xdb, 0xdd, 0xde, 0xde, 0xdf,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xe5, 0xe6, 0xe6, 0xe7, 0xe9, 0xea, 0xea, 0xeb, 0xe9, 0xea, 0xea, 0xeb, 0xed, 0xee, 0xee, 0xef,
  0xf5, 0xf6, 0xf6, 0xf7, 0xf9, 0xfa, 0xfa, 0xfb, 0xf9, 0xfa, 0xfa, 0xfb, 0xfd, 0xfe, 0xfe, 0xff,
};

GColor8 gcolor_blend(GColor8 src_color, GColor8 dest_color, uint8_t blending_factor) {
  // Mask for masking out alpha channel and retrieving number of the color
  const uint8_t MASK_RGB = 0b00111111;

  switch (blending_factor) {
    case 0:
      // Fast path: 0%, no-op!
      return dest_color;
    case 1:
      // Lookup: 33%
      return (GColor8) {
        .argb = s_blending_lookup_33_percent[(dest_color.argb & MASK_RGB) +
                                            RGB_LOOKUP_TABLE_SIZE * (src_color.argb & MASK_RGB)],
      };
    case 2:
      // Lookup: 66% - same as mirrored 33% results
      return (GColor8) {
        .argb = s_blending_lookup_33_percent[(src_color.argb & MASK_RGB) +
                                            RGB_LOOKUP_TABLE_SIZE * (dest_color.argb & MASK_RGB)],
      };
    case 3:
      // Fast path: 100%
      return src_color;
    default:
      // Something went utterly wrong - proceed to throw up
      WTF;
  }
}

GColor8 gcolor_alpha_blend(GColor8 src_color, GColor8 dest_color) {
  return gcolor_blend(src_color, dest_color, src_color.a);
}

void gcolor_tint_luminance_lookup_table_init(
    GColor8 tint_color, GColor8 *lookup_table_out) {
  PBL_ASSERTN(lookup_table_out);

  // Inverting the tint color this way inverts the alpha channel too, but we set the alpha of all
  // colors in the lookup table to the original tint color's alpha in the loop below
  const GColor8 inverted_tint_color = (GColor8) { .argb = ~tint_color.argb };

  for (GColor8Component luminance_index = 0; luminance_index < GCOLOR8_COMPONENT_NUM_VALUES;
       luminance_index++) {
    GColor8 blended_color = gcolor_blend(inverted_tint_color, tint_color, luminance_index);
    // Preserve the alpha of the tint color after the blend
    blended_color.a = tint_color.a;
    lookup_table_out[luminance_index] = blended_color;
  }
}

GColor8 gcolor_perform_lookup_using_color_luminance_and_multiply_alpha(
    GColor8 src_color, const GColor8 lookup_table[GCOLOR8_COMPONENT_NUM_VALUES]) {
  PBL_ASSERTN(lookup_table);

  const GColor8Component src_color_luminance = gcolor_get_luminance(src_color);
  GColor8 result = lookup_table[src_color_luminance];
  result.a = gcolor_component_multiply(src_color.a, result.a);
  return result;
}

GColor8 gcolor_tint_using_luminance_and_multiply_alpha(GColor8 src_color, GColor8 tint_color) {
  GColor8 tint_luminance_lookup_table[GCOLOR8_COMPONENT_NUM_VALUES];
  gcolor_tint_luminance_lookup_table_init(tint_color, tint_luminance_lookup_table);
  return gcolor_perform_lookup_using_color_luminance_and_multiply_alpha(
      src_color, tint_luminance_lookup_table);
}

static const GColor8Component s_color_component_multiplication_lookup[16] = {
  0, 0, 0, 0,
  0, 0, 1, 1,
  0, 1, 1, 2,
  0, 1, 2, 3,
};

GColor8Component gcolor_component_multiply(GColor8Component a, GColor8Component b) {
  // TODO PBL-37522: Benchmark using arithmetic for this expression
  return s_color_component_multiplication_lookup[(a << 2) | b];
}

void grange_clip(GRange *range_to_clip, const GRange * const range_clipper) {
  int16_t start = range_to_clip->origin;
  int16_t end = range_to_clip->origin + range_to_clip->size;
  start = CLIP(start, range_clipper->origin, range_clipper->origin + range_clipper->size);
  end = CLIP(end, range_clipper->origin, range_clipper->origin + range_clipper->size);
  range_to_clip->origin = start;
  range_to_clip->size = end - start;
}
