########################################################################
##
## Copyright (C) 2019-2020 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave 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 3 of the License, or
## (at your option) any later version.
##
## Octave 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 Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {@var{rgb} =} insertText (@var{img}, @var{pos}, @var{txt})
## @deftypefnx {} {@var{rgb} =} insertText (@dots{}, @var{param1}, @var{value1}, @dots{})
## Insert text in image.
##
## @seealso{text}
## @end deftypefn

function rgb = insertText (img, pos, txt, varargin)

  if (nargin < 3 || mod (numel (varargin), 2) || nargout > 1)
    print_usage ();
  endif

  if (! isnumeric (img) || ! isreal (img) || ...
      ! (ismatrix (img) || (ndims (img) ==3 && size (img, 3) == 3)))
    error ("insertText: IMG must be an RGB or grayscale image")
  endif

  if (! ischar (txt) && ! iscellstr (txt) && ! isnumeric (txt))
    error ("insertText: TXT must be a character array or a cell array of strings");
  endif
  if (ischar (txt))
    txt = cellstr (txt);
  endif
  if (isnumeric (txt))
    txt = arrayfun (@(x) sprintf ("%0.5g", x), txt, "UniformOutput", false);
  endif

  if (! isnumeric (pos) || !ismatrix (pos) || size (pos, 2) != 2)
    error ("insertText: POS must be an array of coordinates");
  endif
  if (numel (txt) != size (pos, 1))
    if (numel (txt) > 1)
      error ("insertText: POS must be an array of coordinates");
    else
      txt = repmat (txt, 1, size (pos, 1));
    endif
  endif

  options = containers.Map (...
    {"font", "fontsize", "textcolor", "boxcolor", "boxopacity", "anchorpoint"}, ...
    {"LucidaSansRegular", 12, "black", "yellow", 0.6, "lefttop"});

  anchor_point = containers.Map(...
    {"lefttop", "leftcenter", "leftbottom", "centertop", "center", "centerbottom", "righttop", "rightcenter", "rightbottom"}, ...
    {[0 0], [0 0.5], [0 1] ,[0.5 0], [0.5 0.5], [0.5 1], [1 0], [1 0.5], [1 1]});

  for i=1:2:numel (varargin)
    [param, value] = deal (varargin{i:i+1});
    if (! ischar (param))
      error ("insertText: parameter must be a string");
    endif
    param = tolower (param);
    switch (param)
      case "font"
        if (! ischar (value) || ! isrow (value))
          error ("insertText: Font must be a string");
        endif
      case "fontsize"
        if (! isnumeric (value) || ! isscalar (value) || value < 1)
          error ("insertText: FontSize must be a positive scalar value");
        endif
      case {"textcolor", "boxcolor"}
        if (! ischar (value) && ! iscellstr (value) &&
            ! (isnumeric (value) && ismatrix (value) && size (value, 2) == 3))
          error ("insertText: %s must be RGB values or color names", param);
        endif
      case "boxopacity"
        if (! isnumeric (value) || ! isscalar (value) || value < 0 || value > 1)
          error ("insertText: BoxOpacity must be a scalar between 0 and 1");
        endif
      case "anchorpoint"
        if (!ischar (value) || ! ismember (tolower (value), keys (anchor_point)))
          error ("insertText: AnchorPoint must be a string");
        endif
        value = tolower (value);
      otherwise
        error ("insertText: unknown optional argument");
    endswitch
    options(param) = value;
  endfor

  ## Output RGB image
  if (ismatrix (img))
    rgb = repmat (img, [1 1 3]);
  endif
  if (isinteger (img))
    rgb = double (img) / 255;
  endif

  ## TextColor
  text_color = options("textcolor");
  if (ischar (text_color) || iscellstr(text_color))
    text_color = str2rgb (text_color);
  endif
  if (size (text_color,1) != size (pos, 1))
    if (size (text_color, 1) > 1)
      error ("insertText: TextColor must be RGB values or color names");
    else
      text_color = repmat (text_color, size (pos, 1), 1);
    endif
  endif

  ## BoxColor
  if (options("boxopacity") > 0)
    box_color = options("boxcolor");
    if (ischar (box_color) || iscellstr(box_color))
      box_color = str2rgb (box_color);
    endif
    
    if (size (box_color, 1) != size (pos, 1))
      if (size (box_color, 1) > 1)
        error ("insertText: BoxColor must be a RGB values or color names");
      else
        box_color = repmat (box_color, size (pos, 1), 1);
      endif
    endif
  endif

  for i=1:numel (txt)

    ## Get text
    RGBA = __text_to_pixels__ (txt{i}, options("font"), options("fontsize"));
    alpha = double (flipud (permute (RGBA(4,:,:), [3 2 1]))) / 255;

    ## Pad for box
    if (options("boxopacity") > 0)
      pad_size = round (0.5 * size (alpha, 1));
      alpha = padarray (alpha, [pad_size pad_size], 0);
    endif

    ## Get indices
    ap = anchor_point(options("anchorpoint"));
    bbox = [0, 0, size(alpha, 2), size(alpha, 1)];
    bbox = [round(pos(i,:)-ap.*bbox(3:4)), bbox(3:4)];
    idx_row = bbox(2):(bbox(2)+bbox(4)-1);
    idx_col = bbox(1):(bbox(1)+bbox(3)-1);

    ## Crop
    idx = idx_row < 1 | idx_row > size (rgb, 1);
    alpha(idx,:) = idx_row(idx) = [];
    idx = idx_col < 1 | idx_col > size (rgb, 2);
    alpha(:,idx) = idx_col(idx) = [];

    ## Insert box
    if (options("boxopacity") > 0)
      for k=1:3
        rgb(idx_row, idx_col, k) = rgb(idx_row, idx_col, k) * (1-options("boxopacity")) + box_color(i, k) * options("boxopacity");
      endfor
    endif

    ## Insert text
    for k=1:3
      rgb(idx_row, idx_col, k) = rgb(idx_row, idx_col, k) .* (1-alpha) + alpha * text_color(i, k);
    endfor

  endfor

endfunction

function rgb = str2rgb (str)
  str = cellstr (str);
  rgb = zeros (numel (str), 3);
  for i=1:numel (str)
    switch (tolower (str{i}))
      case "blue"
        rgb(i,:) = [0 0 1];
      case "black"
        rgb(i,:) = [0 0 0];
      case "red"
        rgb(i,:) = [1 0 0];
      case "green"
        rgb(i,:) = [0 1 0];
      case "yellow"
        rgb(i,:) = [1 1 0];
      case "magenta"
        rgb(i,:) = [1 0 1];
      case "cyan"
        rgb(i,:) = [0 1 1];
      case "white"
        rgb(i,:) = [1 1 1];
      otherwise
        error ("insertText: invalid color");
    endswitch
  endfor
endfunction

%!test
%! rgb = insertText (rand (64), [8 16], "Octave");
%! rgb = insertText (rand (64), [8 16], pi);
%! rgb = insertText (rand (64), [8 16], "Octave", "Font", "FreeSerif");
%! rgb = insertText (rand (64), [8 16], "Octave", "FontSize", 42);
%! rgb = insertText (rand (64), [8 16], "Octave", "TextColor", "yellow");
%! rgb = insertText (rand (64), [8 16], "Octave", "TextColor", [1 1 0]);
%! rgb = insertText (rand (64), [8 16], "Octave", "BoxColor", "magenta");
%! rgb = insertText (rand (64), [8 16], "Octave", "BoxColor", [1 0 1]);
%! rgb = insertText (rand (64), [8 16], "Octave", "BoxOpacity", 0.4);
%! rgb = insertText (rand (64), [8 16], "Octave", "BoxOpacity", 0);
%! rgb = insertText (rand (64), [8 16], "Octave", "BoxOpacity", 1);
%! rgb = insertText (rand (64), [8 16], "Octave", "AnchorPoint", "Center");
%! rgb = insertText (rand (64), [8 16], repmat ("Octave", 1, 20)); # check crop
%! rgb = insertText (rand (64), [32 16; 32 48], {"GNU","Octave"}, "AnchorPoint", "Center");
%! rgb = insertText (rand (64), [32 16; 32 48], [pi, exp(1)], "AnchorPoint", "Center");
%! rgb = insertText (rand (64), [8 8; 8 32], {"Octave"});
%! rgb = insertText (rand (64), [8 8; 8 32], "Octave", "BoxColor", [1 0 0;0 1 0]);
%! rgb = insertText (rand (64), [8 8; 8 32], "Octave", "BoxColor", "BlUe");
%! rgb = insertText (rand (64), [8 8; 8 32], "Octave", "BoxColor", "white", "TextColor", {"cyan", "green"});

## Test input validation
%!error <Invalid call> insertText ()
%!error <Invalid call> insertText (1)
%!error <Invalid call> insertText (1, 2)
%!error <Invalid call> insertText (1, 2, 3, 4)
%!error <Invalid call> [a, b] = insertText (1, 2, 3)
%!error rgb = insertText (rand (64), [8 16], "Octave", "Font", 12);
%!error rgb = insertText (rand (64), [8 16], "Octave", "FontSize", "12");
%!error rgb = insertText (rand (64), [8 16], "Octave", "TextColor", [1 2 3 4]);
%!error rgb = insertText (rand (64), [8 16], "Octave", "BoxColor", "pink");
%!error rgb = insertText (rand (64), [8 16], "Octave", "BoxOpacity", 1.2);
%!error rgb = insertText (rand (64), [8 16], "Octave", "AnchorPoint", "Middle");
%!error rgb = insertText (rand (64), [8 16], "Octave", "FontWeight", "bold");
%!error rgb = insertText (rand (64), [8 8; 8 32], "Octave", "BoxColor", eye(3));
%!error rgb = insertText (rand (64), [8 8; 8 32], "Octave", "FontColor", eye(3));
