/*
 * UPIWIN - Micro Pi Windowing Framework Kernel
 * Copyright (C) 2019 Amy Bowersox/Erbosoft Metaverse Design Solutions
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *-------------------------------------------------------------------------
 */
#include <stddef.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include "config.h"
#include "log.h"
#include "fbinit.h"
#include "scode.h"
#include "resources.h"

static int fb_fd = -1;   /* framebuffer file descriptor */

/* frame buffer information */
static FBINFO local_info;
PCFBINFO Fb_Info = &local_info;

UINT16 *Fb_Ptr = NULL;   /* pointer to memory-mapped frame buffer */

inline static UINT16 makemask(unsigned offset, unsigned length)
{
  return (UINT16)(((1 << length) - 1) << offset);
}

static void do_cleanup(void)
{
  /* black out the display */
  memset(Fb_Ptr, 0, local_info.screenbytes);

  munmap((void *)Fb_Ptr, local_info.screenbytes);
  Fb_Ptr = NULL;
  close(fb_fd);
  fb_fd = -1;
}

HRESULT Fb_setup(void)
{
  HRESULT hr = S_OK;
  HRESULT hr2 = S_OK;
  HRSRC splash;
  struct fb_fix_screeninfo fixed;
  struct fb_var_screeninfo var;

  fb_fd = open(Gconfig.framebuffer_device, O_RDWR);
  if (fb_fd == -1)
  {
    hr = ERRNO_AS_SCODE;
    Log(LFATAL, "Unable to open framebuffer device %s (%08X)", Gconfig.framebuffer_device, hr);
    return hr;
  }

  /* fixed info is needed to get the memory parameters for the display */
  if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &fixed))
  {
    hr = ERRNO_AS_SCODE;
    Log(LFATAL, "Could not get fixed screen info (%08X)", hr);
    return hr;
  }

  local_info.linebytes = fixed.line_length;
  local_info.screenbytes = fixed.smem_len;

  /* variable info is used to get screen geometry and color info */
  if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var))
  {
    hr = ERRNO_AS_SCODE;
    Log(LFATAL, "Could not get variable screen info (%08X)", hr);
    return hr;
  }

  local_info.width = var.xres;
  local_info.height = var.yres;
  local_info.virtual_width = var.xres_virtual;
  local_info.virtual_height = var.yres_virtual;
  local_info.bpp = var.bits_per_pixel;
  local_info.red_offset = var.red.offset;
  local_info.red_length = var.red.length;
  local_info.red_mask = makemask(var.red.offset, var.red.length);
  local_info.green_offset = var.green.offset;
  local_info.green_length = var.green.length;
  local_info.green_mask = makemask(var.green.offset, var.green.length);
  local_info.blue_offset = var.blue.offset;
  local_info.blue_length = var.blue.length;
  local_info.blue_mask = makemask(var.blue.offset, var.blue.length);

  Fb_Ptr = (UINT16 *)mmap(0, fixed.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fb_fd, 0);
  if ((int)Fb_Ptr == -1)
  {
    hr = ERRNO_AS_SCODE;
    Log(LFATAL, "Unable to memmap framebuffer (%08X)", hr);
    Fb_Ptr = NULL;
    close(fb_fd);
    fb_fd = -1;
    return hr;
  }

  /* The splash screen is in the system resources.  Use the resource API to load it straight to the frame buffer. */
  hr2 = Rsrc_find_resource((HRESFILE)NULL, "splash.bin", NULL, &splash);
  if (SUCCEEDED(hr2))
  {
    ASSERT(Rsrc_sizeof_resource(splash) == fixed.smem_len);
    hr2 = Rsrc_read_resource_here(splash, Fb_Ptr, fixed.smem_len, NULL);
    Rsrc_free_resource(splash);
  }
  if (FAILED(hr2))
    Log(LWARN, "splash screen rendering failed (%08X)", hr2);

  hr = Config_exitfunc(do_cleanup);
  if (FAILED(hr))
    do_cleanup();
  return hr;
}

void Fb_clear(void)
{
  memset(Fb_Ptr, 0, local_info.screenbytes);
}
