function wgame(mode,varargin)

% [NOTE 30.06.2017, amerle]
% Introduced persistent ZCLEANUP object to try and recover the state of
% wgame when the persistent variables are cleared. This happens for example
% when the .m file is modified.
% Previous behavior would in most cases result in having two wgame windows
% open.
% It is approximately working. Approximately because when cleared, the
% action is triggered twice, I imagine this is linked with the
% implementation of the clear function and we are just lucky that it does
% not enter an infinite recursion loop.
% Another option is to trigger the quit action with the old figure handle
% to force it to be closed.

persistent HFIG PAN KPAN FULL ZCLEANUP

% Check state of persistent variables unless we are quitting.
if (~isscalar(HFIG) || ~ishghandle(HFIG) || isempty(PAN) || isempty(KPAN) || isempty(FULL) ...
        || isempty(ZCLEANUP)) && ~(nargin > 0 && strcmpi(mode,'quit'))
 
 % Attempt recovery after variables were cleared
 if nargin > 1 && strcmpi(mode,'recover')
  hfig = varargin{1};
  if isscalar(hfig) && ishghandle(hfig)
   % Recovery successful
   HFIG = hfig;
   % Recovery object will be recreated
   ZCLEANUP = onCleanup(@() wgame('recover',HFIG));
  else
   % Recovery unsuccessful -> Re-init all variables and return
   clear wgame
   return
  end
 end
 
 % Check state of PAN
 if isempty(PAN)
  PAN = {'FRINGES-GAS' {'\PCS::DRAW_REFS:REF_021','\PCS::DRAW_FEEDFOR_GAS:ALIM_001'};
         'E I'          '\PCS::DRAW_FEEDFOR_E_I:*';
         'E U'          '\PCS::DRAW_FEEDFOR_E_U:*';
         'F I'          '\PCS::DRAW_FEEDFOR_F_I:*';
         'F U'          '\PCS::DRAW_FEEDFOR_F_U:*';
         'OH I'         '\PCS::DRAW_FEEDFOR_OH_I:*';
         'OH U'         '\PCS::DRAW_FEEDFOR_OH_U:*';
         'TOR I'        '\PCS::DRAW_FEEDFOR_TOR_I:*';
         'FPS'         {'\PCS::DRAW_FPS:UDCREF_001','\PCS::DRAW_FPS:UREF_001','\PCS::DRAW_FPS:IREF_001'};
         'GAS'          '\PCS::DRAW_FEEDFOR_GAS:*';
         'Gas++'       {'\PCS::DRAW_REFS:REF_021','\PCS::DRAW_FEEDFOR_GAS:ALIM_001','\PCS::DRAW_FEEDFOR_GAS:ALIM_002','\PCS::DRAW_FEEDFOR_GAS:ALIM_003'};
         'REF 1-9'      '\PCS::DRAW_REFS:REF_00*';
         'REF 10-19'    '\PCS::DRAW_REFS:REF_01*';
         'REF 20-29'    '\PCS::DRAW_REFS:REF_02*';
         'FRINGES'      '\PCS::DRAW_REFS:REF_021';
         'PHYS'         '\PCS::DRAW_PHYS:*';
         'DENS_THRESH' {'\PCS::DRAW_PHYS:PHYS_008','\PCS::DRAW_PHYS:PHYS_007','\PCS::DRAW_PHYS:PHYS_010','\PCS::DRAW_PHYS:PHYS_009'};
         'PERT 1-9'     '\PCS::DRAW_PERTS:PERT_00*';
         'PERT 10-19'   '\PCS::DRAW_PERTS:PERT_01*';
         'PERT 20-29'   '\PCS::DRAW_PERTS:PERT_02*';
         'THRESHOLDS'   '\PCS::DRAW_THRESH:*';
         'MATRICES'     '\PCS::PHYS_MAT_ADDRESSES:*';
         'ECH'          '\PCS::DRAW_FEEDFOR_ECRH:*';
         'ARGON'        '\PCS::DRAW_FEEDFOR_GAS:ALIM_002'};
  PAN = struct('label',PAN(:,1),'path',PAN(:,2));
  for k = 1:length(PAN)
   PAN(k).node = deblank(mdsdata('GETNCI($1,"PATH")',PAN(k).path));
   if ischar(PAN(k).node), PAN(k).node = {PAN(k).node}; end
   PAN(k).name = cell(length(PAN(k).node),1);
   PAN(k).lim  = NaN(2,length(PAN(k).node));
   PAN(k).grid = NaN(1,length(PAN(k).node));
   for l = 1:length(PAN(k).node)
    PAN(k).name{l}  = mdsdata('IF_ERROR(GETNCI($1,"RECORD"),"No Name")',[PAN(k).node{l} ':LABEL']);
   end
  end
 end

 % Check state of HFIG
 if ~isscalar(HFIG) || ~ishghandle(HFIG)
  HFIG = figure(...
    'menubar','none','numbertitle','off','position',[945 10 600 865],...
    'DefaultAxesColor',0*[1 1 1],'DefaultAxesXColor',1*[1 1 1],'DefaultAxesYColor',1*[1 1 1],...
    'Color',0.5*[1 1 1]);
  % For versions 8.4.0 and later, set the grid color as well (for
  % earlier versions the parameter did not exist and the grid had
  % the same color as the X and Y axes
  if ~verLessThan('matlab','8.4.0'),set(HFIG,'DefaultAxesGridColor',1*[1 1 1]);end
  %
  set(HFIG,'windowbuttondownfcn','wgame btndwn','windowbuttonupfcn','wgame btnup',...
    'resizefcn','wgame resize','deletefcn',{@(~,~) wgame('quit',HFIG,'delete')});
  h = uimenu('label','Shot');
  uimenu(h,'label','Load',            'accelerator','L','callback','wgame pan')
  uimenu(h,'label','Save',            'accelerator','S','callback','wgame apply')
  uimenu(h,'label','Zero 8th track',  'accelerator','0','callback','wgame apply')
  uimenu(h,'label','View full traces','accelerator','F','callback','wgame full','separator','on','Checked','off')
  uimenu(h,'label','Close',           'accelerator','Q','callback','wgame quit','separator','on')
  h = uimenu('label','Panel');
  for k = 1:length(PAN)
   tmp = k; if k > 9, tmp = []; end
   uimenu(h,'label',PAN(k).label,'accelerator',int2str(tmp),'tag','pan','userdata',k,'callback',['wgame pan ' int2str(k)])
  end
  
  h = uimenu('label','Knot');
  uimenu(h,'label','Select - Mouse Left')
  uimenu(h,'label','Move - Mouse Middle')
  uimenu(h,'label','New - Mouse Right')
  uimenu(h,'label','Previous Knot',                'accelerator','G','callback','wgame selknot -1','separator','on')
  uimenu(h,'label','Next Knot',                    'accelerator','H','callback','wgame selknot  1')
  uimenu(h,'label','Previous Wave',                'accelerator','T','callback','wgame selwave -1')
  uimenu(h,'label','Next Wave',                    'accelerator','B','callback','wgame selwave  1')
  uimenu(h,'label','Delete Knot - Select Previous','accelerator','Z','callback','wgame del     -1','separator','on')
  uimenu(h,'label','Delete Knot - Select Next',    'accelerator','X','callback','wgame del      0')
  uimenu(h,'label','Lock Horizontal',              'accelerator','Y','callback','wgame onoff','tag','hlock','separator','on')
  uimenu(h,'label','Lock Vertical',                'accelerator','U','callback','wgame onoff','tag','vlock')
  
  uicontrol('style','text','tag','txtw')
  uicontrol('style','edit','tag','txtx','callback','wgame txt')
  uicontrol('style','edit','tag','txty','callback','wgame txt')
  
  % Persistent cleanup object. Clearing wgame will clear persistent variable
  % and trigger the cleanup action.
  % The cleanup object will try to recover the original figure when possible.
  ZCLEANUP = onCleanup(@() wgame('recover',HFIG));
 end
 
 % Check state of FULL
 if isempty(FULL)
  FULL = false;
  set(findobj(HFIG,'label','View full traces'),'Checked','off');
 end
 
 % Check state of KPAN
 if isscalar(KPAN)
  wgame('pan',int2str(KPAN))
 else
  KPAN=1;
  wgame pan 1
 end
 
 set(HFIG,'Handlevisibility','off');
end

if (isscalar(HFIG) && ishghandle(HFIG))
 figvis = get(HFIG,'Handlevisibility');
 set(HFIG,'Handlevisibility','on');
 ax = get(HFIG,'CurrentAxes');
end

if nargin
 switch mode

  case 'pan'
   if nargin == 1, kpan = KPAN; else, kpan = str2double(varargin{1}); end
   if isempty(kpan), return, end
   if ~isempty(findobj(HFIG,'type','line','tag','wave','color','r','markerfacecolor','r'))
    switch questdlg('Save this Panel','wgame')
     case 'Yes', wgame apply
     case 'Cancel', return
    end
   end 
   KPAN = kpan;
   set(HFIG,'name',PAN(kpan).label)
   delete(findobj(HFIG,'type','axes'))
   for l = 1:length(PAN(kpan).node)
    x = mdsdata('IF_ERROR(GETNCI($1,"RECORD"),[$ROPRAND,$ROPRAND])',[PAN(kpan).node{l} ':LIMITS']);
    k = find(isnan(x)); x(k) = Inf * (2*k-3);
    PAN(kpan).lim(:,l) = x;
    PAN(kpan).grid(l)  = mdsdata('IF_ERROR(GETNCI($1,"RECORD"),$ROPRAND)',[PAN(kpan).node{l} ':GRID']);
    PAN(kpan).trk7(l)  = mdsdata(['IF_ERROR(ANY(' PAN(kpan).node{l} ':TRACK_007!=0.0),$TRUE)']);
    y = mdsdata('if_error(execute($1),$2)',PAN(kpan).node{l},[0 0]);
    x = mdsdata('if_error(execute($1),$2)',['dim_of(',PAN(kpan).node{l},')'],[0 1]);
    ax=axes('userdata',struct('name',PAN(kpan).name{l},'sel',[],'lim',[-8 8 PAN(kpan).lim(:,l)'],'delta',[.001 PAN(kpan).grid(l)]),...
      'xticklabel','','box','on','xgrid','on','ygrid','on','Parent',HFIG);
    if PAN(kpan).trk7(l), col = [.5 .5 1]; else, col = [.5 1 .5]; end
    line(x,y,'linestyle','-','marker','s','color',col,'markerfacecolor',col,'userdata',PAN(kpan).node{l},'tag','wave','Parent',ax);
    ylabel(ax,PAN(kpan).name{l},'interpreter','none','fontsize',7);
   end
   set(ax,'xticklabelmode','auto'); % Last axes created
   set([findobj(HFIG,'tag','txtw') findobj(HFIG,'tag','txtx') findobj(HFIG,'tag','txty')],'string','')
   wgame resize

  case 'apply'
   for h = findobj(HFIG,'type','line','tag','wave')'
    if isempty(strfind(get(h,'userdata'),'PHYS_MAT_ADDRESSES')),
     if mdsdata('WAVE_PUT($1,ANY(GETNCI($1//":TRACK_007","RECORD")!=0.0)&$4==0?F_FLOAT($2):[REPLICATE(SET_RANGE(,1,F_FLOAT($2)),1,7),ZERO(SHAPE($2),F_FLOAT(0.0))],F_FLOAT($3))',...
                 get(h,'userdata'),get(h,'ydata'),get(h,'xdata'),nargin)
      uiwait(errordlg(['Error putting node ' get(h,'userdata')],'','modal'))
     end
    else
     if mdsdata('WAVE_PUT($1,F_FLOAT($2),F_FLOAT($3))',...
                 get(h,'userdata'),get(h,'ydata'),get(h,'xdata'))
      uiwait(errordlg(['Error putting node ' get(h,'userdata')],'','modal'))
     end
    end
   end
   set(findobj(HFIG,'type','line','tag','wave','color','r','markerfacecolor','r'),'color',[.5 .5 1],'markerfacecolor',[.5 .5 1])
   wgame pan
   mgams load

  case 'quit'
   % If closing a WGAME figure
   % Check if figure in second argument is the current WGAME figure
   if nargin > 1 && ~isequal(HFIG,varargin{1})
     % If not, delete carefully the figure
     checkdelete(varargin{1});
     % Different WGAME session so return now
     return;
   end
   % Avoid triggering the quit action again
   if ishghandle(HFIG), set(HFIG,'DeleteFcn',[]);end
   % Delete carefully the figure
   checkdelete(HFIG);
   % Re-init all variables and return
   clear wgame
   return

  case 'selknot'
   d = str2double(varargin{1});
   [x,~,k,~,~,w] = knotsel(ax,0,0);
   w.sel = min(max(k+d,1),length(x));
   set(ax,'userdata',w)
   knotsel(ax,0,1);

  case 'selwave'
   d = str2double(varargin{1});
   if isempty(KPAN), return, end
   [x,~,k] = knotsel(ax,0,0);
   if isempty(k), return, end
   x1 = x(k);
   h = sort(findobj(HFIG,'type','axes'));
   ax=h(min(max(find(h==ax)+d,1),length(h)));
   axes(ax);
   [x,~,~,~,~,w] = knotsel(ax,0,0);
   [~,k] = min(abs(x-x1));
   w.sel = k;
   set(ax,'userdata',w);
   knotsel(ax,0,1);

  case 'del'
   d = str2double(varargin{1});
   [x,y,k,~,h,w] = knotsel(ax,0,0);
   if ~isempty(k)
    x(k) = []; y(k) = [];
    set(h,'xdata',x,'ydata',y,'color','r','markerfacecolor','r')
    w.sel = min(k+d,length(x));
    if w.sel <= 0, w.sel = []; end
    set(ax,'userdata',w);
   end
   knotsel(ax,0,1);
   wgame pretty

  case 'onoff'
   if strcmp(get(gcbo,'checked'),'on'), set(gcbo,'checked','off')
   else,                                set(gcbo,'checked','on')
   end
   
  case 'txt'
   try
    p = [eval(get(findobj(HFIG,'tag','txtx'),'string')) eval(get(findobj(HFIG,'tag','txty'),'string'))];
    wgame('move',p)
   end
   
  case 'btndwn'
   if ~verLessThan('matlab','8.4.0')
    % See function ginput in versions later than 8.4.0 for emulating a
    % fullcrosshair pointer
    pointer = 'crosshair';
   else
    pointer = 'fullcrosshair';
   end
   set(HFIG,'pointer',pointer)
   switch get(HFIG,'selectiontype')
    case 'normal', knotsel(ax,1,1);
    case 'extend', set(HFIG,'windowbuttonmotionfcn','wgame move')
    case 'alt',    wgame new
   end
   
  case 'btnup'
   set(HFIG,'pointer','default','windowbuttonmotionfcn','')
   
  case 'resize'
   tmp = get(HFIG,'position');
   set(findobj(HFIG,'tag','txtw'),'position',[0   tmp(4)-20 200 20])
   set(findobj(HFIG,'tag','txtx'),'position',[200 tmp(4)-20  75 20])
   set(findobj(HFIG,'tag','txty'),'position',[275 tmp(4)-20  75 20])
   h = findobj(HFIG,'type','axes');
   if ~isempty(h)
    x = round((tmp(4)-50)/length(h)*(0:length(h))+20);
    for k=1:length(h)
     set(h(k),'units','pixel','position',[50 x(k) tmp(3)-60 x(k+1)-x(k)])
    end
    wgame pretty
   end
   
  case 'full'
   if FULL
     FULL = false;
     value = 'off';
   else
     FULL = true;
     value = 'on';
   end
   set(findobj(HFIG,'label','View full traces'),'Checked',value);
   wgame('pretty');
   
  case 'pretty'
   x1 = Inf; x2 = -Inf;
   for h = findobj(HFIG,'type','line')'
    x = get(h,'xdata');
    if ~isempty(x), x1 = min(x1,x(1)); x2 = max(x2,x(end)); end
   end
   test = ~isempty(strfind(lower(PAN(KPAN).label),'gas')) || ...
     ~isempty(strfind(lower(PAN(KPAN).label),'ech'));
   if test && ~FULL
     x1 = max(-0.3,x1);
   end
   set(findobj(HFIG,'type','axes'),'xlim',sort([x1 x2] + [-1 1]*(x2-x1)/20))
   for h = findobj(HFIG,'type','line')'
    x = get(h,'ydata'); x1 = min(x); x2 = max(x);
    try set(get(h,'parent'),'ylim',sort([x1 x2] + [-1 1]*(x2-x1)/20)), end
   end
   
  case 'move'
   [x,y,k,p,h,w] = knotsel(ax,0,0);
   if isempty(k), return, end
   if nargin > 1, p = varargin{1}; end
   [xx,yy] = snap(x,y,k,p,w,1);
   if strcmp(get(findobj(HFIG,'tag','vlock'),'checked'),'off'), x(k) = xx; end
   if strcmp(get(findobj(HFIG,'tag','hlock'),'checked'),'off'), y(k) = yy; end
   set(h,'xdata',x,'ydata',y,'color','r','markerfacecolor','r')
   knotsel(ax,0,0);
   wgame pretty
   
  case 'new'
   [x,y,~,p,h,w] = knotsel(ax,0,0);
   if isempty(x) || p(1) < x(1)
    k = 0;
   else
    k = min(max([find(max(p(1)-x,0)) 1]),length(x));
   end
   [xx,yy] = snap(x,y,k,p,w,0);
   x = [x(1:k) xx x(k+1:end)];
   y = [y(1:k) yy y(k+1:end)];
   set(h,'xdata',x,'ydata',y,'color','r','markerfacecolor','r')
   w.sel = k+1;
   set(ax,'userdata',w)
   knotsel(ax,0,1);
   wgame pretty

 end
end
set(HFIG,'Handlevisibility',figvis)

function [x,y,k,p,h,w] = knotsel(ax,sel,ptr)
p = get(ax,'currentpoint'); p = p(1,1:2);
h = findobj(ax,'type','line','tag','wave');
x = get(h,'xdata'); y = get(h,'ydata');
w = get(ax,'userdata');
if sel
 [~,k] = min(abs(x-p(1)));
 w.sel = k;
 set(ax,'userdata',w)
else
 k = w.sel;
end
if ptr, try set(0,'pointerlocation',ap2rp(ax,[x(k) y(k)])), end, end
fig = get(ax,'Parent');
set(findobj(fig,'tag','txtw'),'string',w.name)
set(findobj(fig,'tag','txtx'),'string',num2str(x(k)))
set(findobj(fig,'tag','txty'),'string',num2str(y(k)))
delete(findobj(fig,'type','line','tag','sel'))
line(x(k),y(k),'marker','s','color','w','markerfacecolor','w','tag','sel','Parent',ax)

function [xx,yy] = snap(x,~,k,p,w,dk)
lim = w.lim;
if k-dk >= 1,         lim(1) = x(k-dk)+w.delta(1); end
if k    <  length(x), lim(2) = x(k+1) -w.delta(1); end
if lim(1) > lim(2) || lim(3) > lim(4)
 xx = []; yy = [];
else
 if ~isnan(w.delta(1)), p(1) = round(p(1)/w.delta(1))*w.delta(1); end
 if ~isnan(w.delta(2)), p(2) = round(p(2)/w.delta(2))*w.delta(2); end
 xx = min(max(p(1),lim(1)),lim(2));
 yy = min(max(p(2),lim(3)),lim(4));
end

function pr = ap2rp(ax,pa)
ap = get(ax,'position');
fig = get(ax,'Parent');
fp = get(fig,'position');
xl = get(ax,'xlim'); yl = get(ax,'ylim');
pr = round((pa - [xl(1) yl(1)]) ./ [xl(2)-xl(1) yl(2)-yl(1)] .* ap(3:4) + ap(1:2) + fp(1:2) - [1 2]);

function checkdelete(fig)
% Check if this figure is still valid.
if isscalar(fig) && ishghandle(fig) && ~strcmpi(get(fig,'BeingDeleted'),'on')
  % And close it
  delete(fig);
end

