/*
 * BRLTTY - A background process providing access to the Linux console (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2003 by The BRLTTY Team. All rights reserved.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation.  Please see the file COPYING for details.
 *
 * Web Page: http://mielke.cc/brltty/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

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

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "misc.h"
#include "scr.h"
#include "scr_help.h"

static int fileDescriptor;
static HelpFileHeader fileHeader;
static short pageNumber;
static unsigned char cursorRow, cursorColumn;
static HelpPageEntry *pageDescriptions;
static unsigned char **pages;
static unsigned char *characters;

static void
close_HelpScreen (void) {
  if (characters) {
    free(characters);
    characters = NULL;
  }
  if (pages) {
    free(pages);
    pages = NULL;
  }
  if (pageDescriptions) {
    free(pageDescriptions);
    pageDescriptions = NULL;
  }
  if (fileDescriptor != -1) {
    close(fileDescriptor);
    fileDescriptor = -1;
  }
}

static int
loadPages (const char *file) {
  int bytesRead;
  unsigned long characterCount = 0; /* total length of formatted help screens */

  if ((fileDescriptor = open(file, O_RDONLY)) == -1) {
    LogError("Help file open");
    goto failure;
  }

  if ((bytesRead = read(fileDescriptor, &fileHeader, sizeof(fileHeader))) == -1) {
    LogError("Help file read");
    goto failure;
  }
  if (bytesRead != sizeof(fileHeader)) {
    LogPrint(LOG_ERR, "Help file corrupt");
    goto failure;
  }
  if (fileHeader.pages < 1) {
    LogPrint(LOG_ERR, "Help file corrupt");
    goto failure;
  }

  if (!(pageDescriptions = calloc(fileHeader.pages, sizeof(*pageDescriptions)))) {
    LogError("Help page descriptions allocation");
    goto failure;
  }
  {
    int page;
    for (page=0; page<fileHeader.pages; page++) {
      HelpPageEntry *description = &pageDescriptions[page];
      if ((bytesRead = read(fileDescriptor, description, sizeof(*description))) == -1) {
        LogError("Help file read");
        goto failure;
      }
      if (bytesRead != sizeof(*description)) {
        LogPrint(LOG_ERR, "Help file corrupt");
        goto failure;
      }
      characterCount += description->rows * description->columns;
    }
  }

  if (!(pages = calloc(fileHeader.pages, sizeof(*pages)))) {
    LogError("Help page addresses allocation");
    goto failure;
  }
  if (!(characters = calloc(characterCount, sizeof(*characters)))) {
    LogError("Help page buffer allocation");
    goto failure;
  }
  pages[0] = characters;
  {
    int page;
    for (page=0; page<(fileHeader.pages-1); page++) {
      const HelpPageEntry *description = &pageDescriptions[page];
      pages[page+1] = pages[page] + (description->rows * description->columns);
    }
  }

  {
    int page;
    for (page=0; page<fileHeader.pages; page++) {
      const HelpPageEntry *description = &pageDescriptions[page];
      unsigned char *buffer = pages[page];
      {
        int row;
        for (row=0; row<description->rows; row++) {
          unsigned char lineLength;
          unsigned char *line = &buffer[row * description->columns];
          if ((bytesRead = read(fileDescriptor, &lineLength, sizeof(lineLength))) == -1) {
            LogError("Help line length read");
            goto failure;
          }
          if (bytesRead != sizeof(lineLength)) {
            LogPrint(LOG_ERR, "Help file corrupt.");
            goto failure;
          }
          if (lineLength) {
            if ((bytesRead = read(fileDescriptor, line, lineLength)) == -1) {
              LogError("Help line read");
              goto failure;
            }
            if (bytesRead != lineLength) {
              LogPrint(LOG_ERR, "Help file corrupt");
              goto failure;
            }
          }
          memset(&line[lineLength], ' ', description->columns-lineLength);
        }
      }
    }
  }

  close(fileDescriptor);
  fileDescriptor = -1;
  return 1;

failure:
  close_HelpScreen();
  return 0;
}

static int
open_HelpScreen (const char *file) {
  if (!pages)
    if (!loadPages(file))
      return 0;
  if ((pageNumber < 0) || (pageNumber >= fileHeader.pages)) pageNumber = 0;
  return 1;
}

static void
setPageNumber_HelpScreen (short page) {
  pageNumber = page;
}

static short
getPageNumber_HelpScreen (void) {
  return pageNumber;
}

static short
getPageCount_HelpScreen (void) {
  return (fileDescriptor != -1)? fileHeader.pages: 0;
}

static void
describe_HelpScreen (ScreenDescription *description) {
  const HelpPageEntry *page = &pageDescriptions[pageNumber];
  description->posx = cursorColumn;
  description->posy = cursorRow;
  description->cols = page->columns;
  description->rows = page->rows;
  description->no = 0;	/* 0 is reserved for help screen */
}

static unsigned char *
read_HelpScreen (ScreenBox box, unsigned char *buffer, ScreenMode mode) {
  const HelpPageEntry *description = &pageDescriptions[pageNumber];
  if (validateScreenBox(&box, description->columns, description->rows)) {
    if (mode == SCR_TEXT) {
       int row;
      for (row=0; row<box.height; row++) {
        memcpy(buffer + (row * box.width),
               pages[pageNumber] + ((box.top + row) * description->columns) + box.left,
               box.width);
      }
    } else {
      memset(buffer, 0X07, box.width*box.height);
    }
    return buffer;
  }
  return NULL;
}

static int
insert_HelpScreen (unsigned short key) {
  switch (key) {
    case KEY_PAGE_UP:
      if (pageNumber > 0) {
        --pageNumber;
        cursorRow = cursorColumn = 0;
        return 1;
      }
      break;
    case KEY_PAGE_DOWN:
      if (pageNumber < (fileHeader.pages - 1)) {
        ++pageNumber;
        cursorRow = cursorColumn = 0;
        return 1;
      }
      break;
    case KEY_CURSOR_UP:
      if (cursorRow > 0) {
        --cursorRow;
        return 1;
      }
      break;
    case KEY_CURSOR_DOWN:
      if (cursorRow < (pageDescriptions[pageNumber].rows - 1)) {
        ++cursorRow;
        return 1;
      }
      break;
    case KEY_CURSOR_LEFT:
      if (cursorColumn > 0) {
        --cursorColumn;
        return 1;
      }
      break;
    case KEY_CURSOR_RIGHT:
      if (cursorColumn < (pageDescriptions[pageNumber].columns - 1)) {
        ++cursorColumn;
        return 1;
      }
      break;
    default:
      break;
  }
  return 0;
}

static int
route_HelpScreen (int column, int row, int screen) {
  const HelpPageEntry *description = &pageDescriptions[pageNumber];
  if (row != -1) {
    if ((row < 0) || (row >= description->rows)) return 0;
    cursorRow = row;
  }
  if (column != -1) {
    if ((column < 0) || (column >= description->columns)) return 0;
    cursorColumn = column;
  }
  return 1;
}

void
initializeHelpScreen (HelpScreen *help) {
  initializeBaseScreen(&help->base);
  help->base.describe = describe_HelpScreen;
  help->base.read = read_HelpScreen;
  help->base.insert = insert_HelpScreen;
  help->base.route = route_HelpScreen;
  help->open = open_HelpScreen;
  help->close = close_HelpScreen;
  help->setPageNumber = setPageNumber_HelpScreen;
  help->getPageNumber = getPageNumber_HelpScreen;
  help->getPageCount = getPageCount_HelpScreen;
  fileDescriptor = -1;
  pageDescriptions = NULL;
  pages = NULL;
  characters = NULL;
  pageNumber = 0;
  cursorRow = 0;
  cursorColumn = 0;
}
