/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2014-2015 - Jay McCarthy
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *
 *  RetroArch 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 Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch 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 RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#include <CoreFoundation/CFString.h>

#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif

#ifdef IOS
#include <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#else
#include <ApplicationServices/ApplicationServices.h>
#endif

#include <file/file_path.h>

#include "../font_driver.h"

#define CT_ATLAS_ROWS 16
#define CT_ATLAS_COLS 16
#define CT_ATLAS_SIZE (CT_ATLAS_ROWS * CT_ATLAS_COLS)

typedef struct coretext_atlas_slot
{
    struct font_glyph glyph;
    unsigned charcode;
    unsigned last_used;
    struct coretext_atlas_slot *next;
} coretext_atlas_slot_t;

typedef struct coretext_renderer
{
   struct font_atlas atlas;
   coretext_atlas_slot_t atlas_slots[CT_ATLAS_SIZE];
   coretext_atlas_slot_t *uc_map[0x100];
   struct font_line_metrics line_metrics;
} ct_font_renderer_t;

static struct font_atlas *font_renderer_ct_get_atlas(void *data)
{
   ct_font_renderer_t *handle = (ct_font_renderer_t*)data;
   if (!handle)
      return NULL;
   return &handle->atlas;
}

static const struct font_glyph *font_renderer_ct_get_glyph(
    void *data, uint32_t charcode)
{
   coretext_atlas_slot_t *atlas_slot = NULL;
   ct_font_renderer_t        *handle = (ct_font_renderer_t*)data;

   if (!handle || charcode >= CT_ATLAS_SIZE)
      return NULL;

   atlas_slot = (coretext_atlas_slot_t*)&handle->atlas_slots[charcode];

   return &atlas_slot->glyph;
}

static void font_renderer_ct_free(void *data)
{
   ct_font_renderer_t *handle = (ct_font_renderer_t*)data;

   if (!handle)
      return;

   free(handle->atlas.buffer);
   free(handle);
}

static bool coretext_font_renderer_create_atlas(CTFontRef face, ct_font_renderer_t *handle)
{
   int max_width, max_height;
   unsigned i;
   size_t bytesPerRow;
   CGGlyph glyphs[CT_ATLAS_SIZE];
   CGRect bounds[CT_ATLAS_SIZE];
   CGSize advances[CT_ATLAS_SIZE];
   float ascent, descent;
   CGContextRef offscreen;
   CFDictionaryRef attr;
   CFTypeRef values[1];
   CFStringRef keys[1];
   void *bitmapData                  = NULL;
   bool ret                          = true;
   size_t bitsPerComponent           = 8;
   UniChar characters[CT_ATLAS_SIZE] = {0};

   values[0]                         = face;
   keys[0]                           = kCTFontAttributeName;

   for (i = 0; i < CT_ATLAS_SIZE; i++)
      characters[i] = (UniChar)i;

   CTFontGetGlyphsForCharacters(face, characters, glyphs, CT_ATLAS_SIZE);

   CTFontGetBoundingRectsForGlyphs(face,
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
         kCTFontOrientationDefault,
#else
         kCTFontDefaultOrientation,
#endif
         glyphs, bounds, CT_ATLAS_SIZE);

   CTFontGetAdvancesForGlyphs(face,
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
         kCTFontOrientationDefault,
#else
         kCTFontDefaultOrientation,
#endif
         glyphs, advances, CT_ATLAS_SIZE);

   ascent = CTFontGetAscent(face);
   descent = CTFontGetDescent(face);

   max_width = 0;
   max_height = 0;

   for (i = 0; i < CT_ATLAS_SIZE; i++)
   {
      int origin_x, origin_y;
      struct font_glyph *glyph = &handle->atlas_slots[i].glyph;

      if (!glyph)
         continue;

      origin_x             = ceil(bounds[i].origin.x);
      origin_y             = ceil(bounds[i].origin.y);

      glyph->draw_offset_x = 0;
      glyph->draw_offset_y = -ascent;
      glyph->width         = ceil(bounds[i].size.width);
      glyph->height        = ceil(bounds[i].size.height);
      glyph->advance_x     = ceil(advances[i].width);
      glyph->advance_y     = ceil(advances[i].height);

      max_width            = MAX(max_width, (origin_x + glyph->width));
      max_height           = MAX(max_height, (origin_y + glyph->height));
   }

   max_height              = MAX(max_height, ceil(ascent+descent));

   handle->atlas.width     = max_width * CT_ATLAS_COLS;
   handle->atlas.height    = max_height * CT_ATLAS_ROWS;

   handle->line_metrics.ascender  = (float)CTFontGetAscent(face);
   handle->line_metrics.descender = (float)CTFontGetDescent(face);
   /* CTFontGetDescent() should return a positive value,
    * but I have seen several reports online of the
    * value being negative
    * > Since I cannot test this on real hardware, add
    *   a simple safety check... */
   handle->line_metrics.descender = (handle->line_metrics.descender < 0.0f) ?
         (-1.0f * handle->line_metrics.descender) : handle->line_metrics.descender;
   handle->line_metrics.height    =
         handle->line_metrics.ascender + handle->line_metrics.descender +
         (float)CTFontGetLeading(face);

   handle->atlas.buffer    = (uint8_t*)
      calloc(handle->atlas.width * handle->atlas.height, 1);

   if (!handle->atlas.buffer)
      return false;

   bytesPerRow = max_width;
   bitmapData  = calloc(max_height, bytesPerRow);
   offscreen   = CGBitmapContextCreate(bitmapData, max_width, max_height,
         bitsPerComponent, bytesPerRow, NULL, kCGImageAlphaOnly);

   CGContextSetTextMatrix(offscreen, CGAffineTransformIdentity);

   attr = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
         sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
         &kCFTypeDictionaryValueCallBacks);

   for (i = 0; i < CT_ATLAS_SIZE; i++)
   {
      char glyph_cstr[2];
      const uint8_t *src;
      uint8_t       *dst;
      unsigned offset_x, offset_y, r, c;
      CFStringRef glyph_cfstr;
      CFAttributedStringRef attrString;
      CTLineRef line;
      struct font_glyph *glyph = &handle->atlas_slots[i].glyph;

      if (!glyph)
         continue;

      glyph->width = max_width;
      glyph->height = max_height;

      offset_x = (i % CT_ATLAS_COLS) * max_width;
      offset_y = (i / CT_ATLAS_COLS) * max_height;

      glyph->atlas_offset_x = offset_x;
      glyph->atlas_offset_y = offset_y;

      glyph_cstr[0] = i;
      glyph_cstr[1] = 0;
      glyph_cfstr   = CFStringCreateWithCString(
            NULL, glyph_cstr, kCFStringEncodingASCII );
      attrString =
         CFAttributedStringCreate(NULL, glyph_cfstr, attr);
      CFRelease(glyph_cfstr);
      glyph_cfstr = NULL;
      line = CTLineCreateWithAttributedString(attrString);
      CFRelease(attrString);
      attrString = NULL;

      memset( bitmapData, 0, max_height * bytesPerRow );
      CGContextSetTextPosition(offscreen, 0, descent);
      CTLineDraw(line, offscreen);
      CGContextFlush( offscreen );

      CFRelease( line );
      line = NULL;

      dst = (uint8_t*)handle->atlas.buffer;
      src = (const uint8_t*)bitmapData;

      for (r = 0; r < max_height; r++ )
      {
         for (c = 0; c < max_width; c++)
         {
            unsigned src_idx = (unsigned)(r * bytesPerRow + c);
            unsigned dest_idx =
               (r + offset_y) * (CT_ATLAS_COLS * max_width) + (c + offset_x);
            uint8_t v = src[src_idx];

            dst[dest_idx] = v;
         }
      }
   }

   CFRelease(attr);
   CGContextRelease(offscreen);

   attr = NULL;
   offscreen = NULL;
   free(bitmapData);

   return ret;
}

static void *font_renderer_ct_init(const char *font_path, float font_size)
{
   char err                       = 0;
   CFStringRef cf_font_path       = NULL;
   CTFontRef face                 = NULL;
   CFURLRef url                   = NULL;
   CGDataProviderRef dataProvider = NULL;
   CGFontRef theCGFont            = NULL;
   ct_font_renderer_t *handle = (ct_font_renderer_t*)
      calloc(1, sizeof(*handle));

   if (!handle || !path_is_valid(font_path))
   {
      err = 1;
      goto error;
   }

   cf_font_path = CFStringCreateWithCString(
         NULL, font_path, kCFStringEncodingASCII);

   if (!cf_font_path)
   {
      err = 1;
      goto error;
   }

   url          = CFURLCreateWithFileSystemPath(
         kCFAllocatorDefault, cf_font_path, kCFURLPOSIXPathStyle, false);
   dataProvider = CGDataProviderCreateWithURL(url);
   theCGFont    = CGFontCreateWithDataProvider(dataProvider);
   face         = CTFontCreateWithGraphicsFont(theCGFont, font_size, NULL, NULL);

   if (!face)
   {
      err = 1;
      goto error;
   }

   if (!coretext_font_renderer_create_atlas(face, handle))
   {
      err = 1;
      goto error;
   }

error:
   if (err)
   {
      font_renderer_ct_free(handle);
      handle = NULL;
   }

   if (cf_font_path)
   {
      CFRelease(cf_font_path);
      cf_font_path = NULL;
   }
   if (face)
   {
      CFRelease(face);
      face = NULL;
   }
   if (url)
   {
      CFRelease(url);
      url = NULL;
   }
   if (dataProvider)
   {
      CFRelease(dataProvider);
      dataProvider = NULL;
   }
   if (theCGFont)
   {
      CFRelease(theCGFont);
      theCGFont = NULL;
   }

   return handle;
}

/* We can't tell if a font is going to be there until we actually
   initialize CoreText and the best way to get fonts is by name, not
   by path. */
static const char *default_font = "Verdana";

static const char *font_renderer_ct_get_default_font(void)
{
   return default_font;
}

static bool font_renderer_ct_get_line_metrics(
      void* data, struct font_line_metrics **metrics)
{
   ct_font_renderer_t *handle   = (ct_font_renderer_t*)data;
   if (!handle)
      return false;
   *metrics = &handle->line_metrics;
   return true;
}

font_renderer_driver_t coretext_font_renderer = {
  font_renderer_ct_init,
  font_renderer_ct_get_atlas,
  font_renderer_ct_get_glyph,
  font_renderer_ct_free,
  font_renderer_ct_get_default_font,
  "font_renderer_ct",
  font_renderer_ct_get_line_metrics
};
