
#ifndef output_c_INCLUDED
#define output_c_INCLUDED

#include "output.h"
#include "tracelog.h"
#include "types.h"
#include "fizmo.h"
#include "i18n.h"
#include "zpu.h"
#include "variable.h"
#include "splint.h"

#ifndef DISABLE_BLOCKBUFFER
#include "blockbuf.h"
#endif //DISABLE_BLOCKBUFFER

#ifndef DISABLE_OUTPUT_HISTORY
#include "history.h"
#endif //DISABLE_OUTPUT_HISTORY


bool lower_window_buffering_active = true;
z_style current_style = Z_STYLE_ROMAN;
z_style upper_window_style = Z_STYLE_ROMAN;
z_style lower_window_style = Z_STYLE_ROMAN;
z_colour current_foreground_colour;
z_colour current_background_colour;
z_colour upper_window_foreground_colour;
z_colour upper_window_background_colour;
z_colour lower_window_foreground_colour;
z_colour lower_window_background_colour;

// The lower two values are initalized by the init in "fizmo.c".
int16_t active_window_number;
z_font current_font  = Z_FONT_NORMAL;
//z_font previous_z_font;
static int upper_window_height = 0;


#ifndef DISABLE_BLOCKBUFFER
static void check_upper_window_blockbuffer(int height)
{
  if (upper_window_buffer == NULL)
  {
    upper_window_buffer
      = create_blockbuffer(
          (int)active_interface->get_screen_width(),
          (int)height,
          Z_STYLE_ROMAN,
          Z_FONT_NORMAL,
          current_foreground_colour,
          current_background_colour);
  }
  else
  {
    blockbuf_resize(
        upper_window_buffer,
        (int)active_interface->get_screen_width(),
        (int)op[0]);
  }
}
#endif // DISABLE_BLOCKBUFFER


void opcode_split_window(void)
{
  TRACE_LOG("Opcode: SPLIT_WINDOW\n");
  TRACE_LOG("Number of upper window lines: %d.\n", op[0]);

  upper_window_height = (int)op[0];

#ifndef DISABLE_BLOCKBUFFER
  if ((ver >= 3) && (ver != 6))
  {
    check_upper_window_blockbuffer((int)op[0]);
  }
#endif // DISABLE_BLOCKBUFFER

  active_interface->split_window((int16_t)op[0]);
}


void opcode_set_window(void)
{
  TRACE_LOG("Opcode: SET_WINDOW\n");
  TRACE_LOG("New window number: %d.\n", op[0]);

  if (
      (upper_window_height == 0)
      &&
      (bool_equal(auto_open_upper_window, true))
     )
  {
    // Zokoban.z5 does set_window(1) without splitting the screen.

    //height = (int)(active_interface->get_screen_height() / 2);
    // -> If the height is larger than 1 or 2, the code above gives
    //    a wrong cursor position after restoring "Lost Pig".

    TRACE_LOG("Opening upper window with 1 line for set_window.\n");

#ifndef DISABLE_BLOCKBUFFER
    check_upper_window_blockbuffer(1);
#endif // DISABLE_BLOCKBUFFER

    active_interface->split_window((int16_t)1);

    upper_window_height = 1;
  }

  active_window_number = (int16_t)op[0];
  active_interface->set_window((int16_t)op[0]);
}


void opcode_erase_window(void)
{
  TRACE_LOG("Opcode: ERASE_WINDOW\n");
  TRACE_LOG("Window to erase: %d.\n", (int16_t)op[0]);
  active_interface->erase_window((int16_t)op[0]);
}


void opcode_erase_line_value(void)
{
  TRACE_LOG("Opcode: ERASE_LINE_VALUE\n");
  active_interface->erase_line_value(op[0]);
}


void opcode_erase_line_pixels(void)
{
  TRACE_LOG("Opcode: ERASE_LINE_PIXELS\n");
  active_interface->erase_line_pixels(op[0]);
}


void opcode_set_cursor(void)
{
  int16_t y = (int16_t)op[0];
  int16_t x = (int16_t)op[1];

  // These two adjustments make "painter.z5" work.
  if (y == 0)
    y = 1;

  if (x == 0)
    x = 1;

  if (ver == 6)
  {
    TRACE_LOG("Opcode: SET_CURSOR_LCW\n");
    active_interface->set_cursor(x, y, op[2]);
  }
  else
  {
    if (active_window_number == 1)
    {
#ifndef DISABLE_BLOCKBUFFER
      if (
          (ver >= 3)
          &&
          (upper_window_buffer != NULL)
          &&
          (x >= 1)
          &&
          (y >= 1)
         )
        set_blockbuf_cursor(upper_window_buffer, x-1, y-1);
#endif // DISABLE_BLOCKBUFFER

      if (
          (y >= upper_window_height)
          &&
          (bool_equal(auto_adapt_upper_window, true))
         )
      {
        TRACE_LOG("Resizing upper window to %d lines for cursor movement.\n",y);

#ifndef DISABLE_BLOCKBUFFER
        if (upper_window_buffer != NULL)
          blockbuf_resize(
              upper_window_buffer,
              (int)active_interface->get_screen_width(),
              (int)y);
#endif // DISABLE_BLOCKBUFFER

        active_interface->split_window(y);

        upper_window_height = (int)y;
      }
    }

    TRACE_LOG("Opcode: SET_CURSOR_LC\n");
    active_interface->set_cursor(y, x, active_window_number);
  }
}


void opcode_get_cursor(void)
{
  TRACE_LOG("Opcode: GET_CURSOR\n");
  store_word(z_mem + op[0]    , active_interface->get_cursor_row());
  store_word(z_mem + op[0] + 1, active_interface->get_cursor_column());
}


void opcode_set_text_style(void)
{
  z_style new_z_style;

  TRACE_LOG("Opcode: SET_TEXT_STYLE\n");

  if (ver >= 4)
  {
    TRACE_LOG("Setting text style to %d.\n", op[0]);

    new_z_style = (int16_t)op[0];

    // REVISIT: Implement warning.
    if ( (new_z_style < 0) || (new_z_style > 15) )
      return;

    current_style = new_z_style;

    active_interface->set_text_style(new_z_style);
  }
}


void opcode_buffer_mode(void)
{
  TRACE_LOG("OPCODE: BUFFER_MODE.\n");
  lower_window_buffering_active = ((int16_t)op[0] != 0 ? true : false);
  active_interface->set_buffer_mode(op[0]);
}


static void process_set_colour_opcode(uint16_t op0, uint16_t op1, uint16_t op2)

{
  z_colour new_foreground_colour;
  z_colour new_background_colour;
  int16_t window_number;

  new_foreground_colour = (int16_t)op0;
  new_background_colour = (int16_t)op1;
  window_number = (int16_t)op2;

  // REVISIT: Implement warning
  if ( (new_foreground_colour < -1) || (new_foreground_colour > 12) )
    new_foreground_colour = 0;

  // REVISIT: Implement warning
  if ( (new_background_colour < -1) || (new_background_colour > 12) )
    new_background_colour = 0;

  if (window_number == -1)
    window_number = active_window_number;

  current_foreground_colour = new_foreground_colour;
  current_background_colour = new_background_colour;

  active_interface->set_colour(
      new_foreground_colour,
      new_background_colour,
      window_number);
}


void opcode_set_colour_fb(void)
{
  TRACE_LOG("OPCODE: SET_COLOUR_FB\n");

  TRACE_LOG("fg:%d, bg:%d.\n", op[0], op[1]);
  process_set_colour_opcode(op[0], op[1], (uint16_t)-1);
}


void opcode_set_colour_fbw(void)
{
  TRACE_LOG("OPCODE: SET_COLOUR_FBW\n");
  process_set_colour_opcode(op[0], op[1], op[2]);
}


void opcode_set_font(void)
{
  z_font new_z_font;

  // If the requested font is available, then it is chosen for the
  // current window, and the store value is the font ID of the previous
  // font (which is always positive). If the font is unavailable,
  // nothing will happen and the store value is 0.

  // Please note: I consider the "normal" -- code 1 -- and the courier,
  // fixed font -- code 4 -- to be always available. For simple interfaces,
  // these are probably the same. The availability of the character graphics
  // and picture font are read from interface definition.

  TRACE_LOG("OPCODE: SET_FONT\n");
  read_z_result_variable();
  new_z_font = (int16_t)op[0];

  // REVISIT: Implement warning.
  if (
      ( (new_z_font < 0) || (new_z_font > 4) )
      ||
      (
       (new_z_font == Z_FONT_PICTURE)
       &&
       (active_interface->is_picture_font_availiable() != true)
      )
      ||
      (
       (new_z_font == Z_FONT_CHARACTER_GRAPHICS)
       &&
       (active_interface->is_character_graphics_font_availiable() != true)
      )
     )
  {
    set_variable(z_res_var, 0);
    return;
  }

  // REVISIT: Is it correct to not double-set fonts?
  if (new_z_font != current_font)
  {
    if (active_window_number == 0)
    {
#ifndef DISABLE_OUTPUT_HISTORY
      store_metadata_in_history(
          outputhistory[0],
          HISTORY_METADATA_TYPE_FONT,
          new_z_font,
          -1);
#endif /* DISABLE_OUTPUT_HISTORY */
    }

    else if (active_window_number == 1)
    {
#ifndef DISABLE_BLOCKBUFFER
      if (ver >= 5)
      {
        set_blockbuf_font(upper_window_buffer, new_z_font);
      }
#endif /* DISABLE_BLOCKBUFFER */
    }

    active_interface->set_font(new_z_font);
    set_variable(z_res_var, (uint16_t)current_font);
    //previous_z_font = current_font;
    current_font = new_z_font;
    TRACE_LOG("New font is: %d\n", current_font);
  }
  else
  {
    //set_variable(z_res_var, (uint16_t)previous_z_font);
    set_variable(z_res_var, (uint16_t)current_font);
  }
}

#endif /* output_c_INCLUDED */

