/****************************************************************************
 * apps/graphics/traveler/src/trv_graphics.c
 *
 *   Copyright (C) 2014, 2016 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "trv_types.h"
#include "trv_main.h"
#include "trv_mem.h"
#include "trv_color.h"
#include "trv_raycntl.h"
#include "trv_debug.h"
#include "trv_graphics.h"

#include <string.h>

#ifdef CONFIG_NX_MULTIUSER
#  include <semaphore.h>
#endif

#ifdef CONFIG_VNCSERVER
#  include <nuttx/video/vnc.h>
#endif

/****************************************************************************
 * Public Data
 ****************************************************************************/

#ifdef CONFIG_NX
FAR const struct nx_callback_s *g_trv_nxcallback;
sem_t g_trv_nxevent = SEM_INITIZIALIZER(0);
bool g_trv_nxresolution = false;
#ifdef CONFIG_NX_MULTIUSER
bool g_trv_nxrconnected = false;
#endif
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: trv_get_fbdev
 *
 * Description:
 *   Get the system framebuffer device
 *
 ****************************************************************************/

#ifndef CONFIG_NX_MULTIUSER
static FAR struct fb_vtable_s *trv_get_fbdev(void)
{
  FAR struct fb_vtable_s *fbdev;
  int ret;

  /* Initialize the frame buffer device */

  ret = up_fbinitialize(0);
  if (ret < 0)
    {
      trv_abort("ERROR: up_fbinitialize() failed: %d\n", -ret);
    }

  /* Set up to use video plane 0.  There is no support for anything but
   * video plane 0.
   */

  fbdev = up_fbgetvplane(0,0);
  if (!fbdev)
    {
      trv_abort("ERROR: up_fbgetvplane() failed\n");
    }

  return fbdev;
}
#endif

/****************************************************************************
 * Name: trv_fb_initialize
 *
 * Description:
 *   Get the system framebuffer device
 *
 ****************************************************************************/

#if !defined(CONFIG_NX_MULTIUSER) && !defined(CONFIG_NX)
static void trv_fb_initialize(FAR struct trv_graphics_info_s *ginfo)
{
  struct fb_videoinfo_s vinfo;
  struct fb_planeinfo_s pinfo;
  FAR struct fb_vtable_s *fbdev;
  int ret;

  /* Get the framebuffer device */

  fbdev = trv_get_fbdev();

  /* Get information about video plane 0 */

  ret = fbdev->getvideoinfo(fbdev, &vinfo);
  if (ret < 0)
    {
      trv_abort("ERROR: getvideoinfo() failed\n");
    }

  ginfo->xres = vinfo.xres;
  ginfo->yres = vinfo.yres;

  ret = fbdev->getplaneinfo(fbdev, 0, &pinfo);
  if (ret < 0)
    {
      trv_abort("ERROR: getplaneinfo() failed\n");
    }

  ginfo->stride   = pinfo.stride;
  ginfo->hwbuffer = pinfo.fbmem;

  if (vinfo.fmt != TRV_COLOR_FMT || pinfo.bpp != TRV_BPP)
    {
      trv_abort("ERROR: Bad color format(%d)/bpp(%d)\n", vinfo.fmt, pinfo.bpp);
    }
}
#endif

/****************************************************************************
 * Name: trv_use_bgwindow
 *
 * Description:
 *   Get the NX background window
 *
 ****************************************************************************/

#ifdef CONFIG_NX
static void trv_use_bgwindow(FAR struct trv_graphics_info_s *ginfo)
{
  /* Get the background window */

  ret = nx_requestbkgd(g_hnx, &g_trv_nxcallback, ginfo);
  if (ret < 0)
    {
      trv_abort("nx_requestbkgd failed: %d\n", errno);
    }

  /* Wait until we have the screen resolution.  We'll have this immediately
   * unless we are dealing with the NX server.
   */

  while (!g_trv_nxresolution)
    {
      (void)sem_wait(&g_trv_nxevent);
    }
}
#endif

/****************************************************************************
 * Name: trv_nxsu_initialize
 ****************************************************************************/

#if defined(CONFIG_NX) && !defined(CONFIG_NX_MULTIUSER)
static inline int trv_nxsu_initialize(FAR struct trv_graphics_info_s *ginfo)
{
  FAR struct fb_vtable_s *fbdev;
  int ret;

  /* Get the framebuffer device */

  fbdev = trv_get_fbdev();

  /* Open NX */

  ginfo->hnx = nx_open(fbdev);
  if (!ginfo->hnx)
    {
      trv_abort("trv_nxsu_initialize: nx_open failed: %d\n", errno);
    }

#ifdef CONFIG_VNCSERVER
  /* Setup the VNC server to support keyboard/mouse inputs */

  ret = vnc_default_fbinitialize(0, ginfo->hnx);
  if (ret < 0)
    {
      nx_close(ginfo->hnx);
      trv_abort("vnc_default_fbinitialize failed: %d\n", ret);
    }
#endif

  /* And use the background window */

  trv_use_bgwindow(ginfo);
}
#endif

/****************************************************************************
 * Name: trv_servertask
 ****************************************************************************/

#ifdef CONFIG_NX_MULTIUSER
int trv_servertask(int argc, char *argv[])
{
  FAR struct fb_vtable_s *fbdev;
  int ret;

  /* Get the framebuffer device */

  fbdev = trv_get_fbdev();

  /* Then start the server */

  ret = nx_run(dev);
  trv_abort("nx_run returned: %d\n", errno);
}
#endif

/****************************************************************************
 * Name: trv_nxmu_initialize
 ****************************************************************************/

#ifdef CONFIG_NX_MULTIUSER
static inline int trv_nxmu_initialize(FAR struct trv_graphics_info_s *ginfo)
{
  struct sched_param param;
  pthread_t thread;
  pid_t servrid;
  int ret;

  /* Set the client task priority */

  param.sched_priority = CONFIG_EXAMPLES_NX_CLIENTPRIO;
  ret = sched_setparam(0, &param);
  if (ret < 0)
    {
      trv_abort("nxeg_initialize: sched_setparam failed: %d\n" , ret);
    }

  /* Start the server task */

  printf("nxeg_initialize: Starting trv_servertask task\n");
  servrid = task_create("NX Server", CONFIG_EXAMPLES_NX_SERVERPRIO,
                        CONFIG_EXAMPLES_NX_STACKSIZE, trv_servertask, NULL);
  if (servrid < 0)
    {
      trv_abort("nxeg_initialize: Failed to create trv_servertask task: %d\n",
                errno);
    }

  /* Wait a bit to let the server get started */

  sleep(1);

  /* Connect to the server */

  ginfo->hnx = nx_connect();
  if (ginfo->hnx)
    {
      pthread_attr_t attr;

#ifdef CONFIG_VNCSERVER
      /* Setup the VNC server to support keyboard/mouse inputs */

      ret = vnc_default_fbinitialize(0, ginfo->hnx);
      if (ret < 0)
        {
          trv_abort("vnc_default_fbinitialize failed: %d\n", ret);
        }
#endif

      /* Start a separate thread to listen for server events.  This is probably
       * the least efficient way to do this, but it makes this example flow more
       * smoothly.
       */

      (void)pthread_attr_init(&attr);
      param.sched_priority = CONFIG_EXAMPLES_NX_LISTENERPRIO;
      (void)pthread_attr_setschedparam(&attr, &param);
      (void)pthread_attr_setstacksize(&attr, CONFIG_EXAMPLES_NX_STACKSIZE);

      ret = pthread_create(&thread, &attr, trv_nxlistener, NULL);
      if (ret != 0)
        {
           trv_abort("nxeg_initialize: pthread_create failed: %d\n", ret);
        }

      /* Don't return until we are connected to the server */

      while (!g_trv_nxrconnected)
        {
          /* Wait for the listener thread to wake us up when we really
           * are connected.
           */

          (void)sem_wait(&g_trv_nxevent);
        }
    }
  else
    {
      trv_abort("nxeg_initialize: nx_connect failed: %d\n", errno);
    }

  /* And use the background window */

  trv_use_bgwindow(ginfo);
}
#endif

/****************************************************************************
 * Name: trv_row_update
 *
 * Description:
 *   Expand one one either directly into the frame buffer or else into an
 *   intermediate line buffer.
 *
 ****************************************************************************/

void trv_row_update(struct trv_graphics_info_s *ginfo,
                    FAR const trv_pixel_t *src,
                    FAR dev_pixel_t *dest)
{
  dev_pixel_t pixel;
  trv_coord_t srccol;
  int i;

  /* Loop for each column in the src render buffer */

  for (srccol = 0; srccol < TRV_SCREEN_WIDTH; srccol++)
    {
      /* Map the source pixel */

      pixel = ginfo->palette.lut[*src++];

      /* Expand pixels horizontally via pixel replication */

      for (i = 0; i < ginfo->xscale; i++)
        {
          *dest++ = pixel;
        }
    }
}

/****************************************************************************
 * Name: trv_row_transfer
 *
 * Description:
 *   Transfer one line from the line buffer to the NX window.
 *
 ****************************************************************************/

#ifdef CONFIG_NX
void trv_display_update(struct trv_graphics_info_s *ginfo,
                        FAR dev_pixel_t *dest, trv_coord_t destrow)
{
  /* Transfer the row buffer to the NX window */
#warning Missing logic

}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: trv_graphics_initialize
 *
 * Description:
 ****************************************************************************/

int trv_graphics_initialize(FAR struct trv_graphics_info_s *ginfo)
{
  int width;
  int height;
  int scale;

  /* Initialize the graphics device and get information about the display */

#if !defined(CONFIG_NX)
  trv_fb_initialize(ginfo);
#elif defined(CONFIG_NX_MULTIUSER)
  trv_nxmu_initialize(ginfo);
#else
  trv_nxsu_initialize(ginfo);
#endif

  /* Check the size of the display */

  width  = ginfo->xres;
  height = ginfo->yres;

  if (width < TRV_SCREEN_WIDTH || height < TRV_SCREEN_HEIGHT)
    {
      trv_abort("ERROR: Display is too small\n");
    }

  /* Check if we need to scale the image */

  scale = 0;

  while (width >= TRV_SCREEN_WIDTH)
    {
      width -= TRV_SCREEN_WIDTH;
      scale++;
    }

  ginfo->xscale   = scale;
  ginfo->xoffset  = (width >> 1);
  ginfo->imgwidth = scale * TRV_SCREEN_WIDTH * sizeof(dev_pixel_t);

  scale = 0;
  while (height >= TRV_SCREEN_HEIGHT)
    {
      height -= TRV_SCREEN_HEIGHT;
      scale++;
    }

  ginfo->yscale  = scale;
  ginfo->yoffset = (height >> 1);

  /* Allocate buffers
   *
   * ginfo->swbuffer - Software renders into this buffer using an 8-bit
   *   encoding and perhaps at a different resolution that the final
   *   image.
   */

   ginfo->swbuffer = (trv_pixel_t*)
     trv_malloc(TRV_SCREEN_WIDTH * TRV_SCREEN_HEIGHT * sizeof(trv_pixel_t));
   if (!ginfo->swbuffer)
     {
       trv_abort("ERROR: Failed to allocate render buffer\n");
     }

  /* Using the framebuffer driver:
   *   ginfo->hwbuffer - This address of the final, expanded frame image.
   *   This address is determined by hardware and is neither allocated
   *   nor freed.
   *
   * Using NX
   *   ginfo->hwbuffer - This address of one line of the final expanded
   *   image that must transferred to the window.
   */

#ifdef CONFIG_NX
   ginfo->hwbuffer = (trv_pixel_t*)trv_malloc(ginfo->imgwidth);
   if (!ginfo->hwbuffer)
     {
       trv_abort("ERROR: Failed to allocate hardware line buffer\n");
     }
#endif

  /* Allocate color mapping information */

  trv_color_allocate(&ginfo->palette);
  trv_vdebug("%d colors allocated\n", ginfo->palette.ncolors);
  return OK;
}

/****************************************************************************
 * Name: trv_graphics_terminate
 *
 * Description:
 ****************************************************************************/

void trv_graphics_terminate(FAR struct trv_graphics_info_s *ginfo)
{
  /* Free palette */

  trv_color_free(&ginfo->palette);

  /* Free image buffers */

  if (ginfo->swbuffer)
    {
      trv_free(ginfo->swbuffer);
      ginfo->swbuffer = NULL;
    }

#ifdef CONFIG_NX
  if (ginfo->hwbuffer)
    {
      trv_free(ginfo->hwbuffer);
      ginfo->hwbuffer = NULL;
    }

  /* Close/disconnect NX */
#warning "Missing Logic"
#endif
}

/****************************************************************************
 * Name: trv_display_update
 *
 * Description:
 ****************************************************************************/

void trv_display_update(struct trv_graphics_info_s *ginfo)
{
  FAR const uint8_t *src;
  FAR uint8_t *dest;
  trv_coord_t srcrow;
#ifdef CONFIG_NX
  trv_coord_t destrow;
#else
  FAR uint8_t *first;
#endif
  int i;

  /* Get the star tof the first source row */

  src = (FAR const uint8_t *)ginfo->swbuffer;

  /* Get the start of the first destination row */

  dest = (FAR uint8_t *)ginfo->hwbuffer +
         (ginfo->yoffset * ginfo->stride) +
         (ginfo->xoffset * sizeof(dev_pixel_t));

  /* Loop for each row in the src render buffer */

#ifdef CONFIG_NX
  destrow = 0;
#endif

  for (srcrow = 0; srcrow < TRV_SCREEN_HEIGHT; srcrow++)
    {
      /* Transfer the row to the device row/buffer */

      trv_row_update(ginfo, (FAR const trv_pixel_t *)src,
                     (FAR dev_pixel_t *)dest);

#ifdef CONFIG_NX
      /* Transfer the row buffer to the NX window */

      trv_row_transfer(ginfo, dest, destrow);
      destrow++;
#else
      first = dest;
      dest += ginfo->stride;
#endif

      /* Then replicate as many times as is necessary */

      for (i = 1; i < ginfo->yscale; i++)
        {
#ifdef CONFIG_NX
          /* Transfer the row buffer to the NX window */

          trv_row_transfer(ginfo, dest, destrow);
          destrow++;
#else
          /* Point to the next row in the frame buffer */

          memcpy(dest, first, ginfo->imgwidth);
          dest += ginfo->stride;
#endif
        }

      /* Point to the next src row */

      src += TRV_SCREEN_WIDTH;
    }
}

