/*
 * ========================================================================
 * Copyright 2006-2008 University of Washington
 * Copyright 2013-2026 Eduardo Chappa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "headers.h"
#include "xoauth2conf.h"
#include "keymenu.h"
#include "status.h"
#include "confscroll.h"
#include "init.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/list.h"
#include "../pith/mailcmd.h"
#include "xoauth2info.c"

typedef enum {Xname = 0, Xid, Xsecret, Xtenant, Xuser, XFlow, Xend} XTYPES;

typedef struct xoauh2_info_val_s {
  char *screen_name;
  char *pinerc_name;
} XOAUTH2_INFO_VAL_S;

/* the order here must match the order in XTYPES above */
XOAUTH2_INFO_VAL_S x_default[] = {
   {NULL, 			XNAME},
   {XOAUTH2_CLIENT_ID,		XID},
   {XOAUTH2_CLIENT_SECRET,	XSECRET},
   {XOAUTH2_TENANT,		XTENANT},
   {XOAUTH2_USERS,		XUSER},
   {XOAUTH2_FLOW,		XFLOW},
   {NULL, 			NULL}
};

char *list_to_array(char **);
char **array_to_list(char *);
void write_xoauth_configuration(struct variable  *, struct variable **, EditWhich);
char **xoauth2_conf_dedup_and_merge(char ***);
XOAUTH2_INFO_S *xoauth_info_choice(XOAUTH2_INFO_S **, char *);
int xoauth2_info_tool(struct pine *, int, CONF_S **, unsigned int);
char *xoauth2_extra_text(XOAUTH2_INFO_S **, int);
XOAUTH2_INFO_S **xoauth2_configured_servers(void);
XOAUTH2_INFO_S **parse_xoauth2_info_list(char **lval, int *totalp);

char *
list_to_array(char **list)
{
   char *rv;
   int i;
   size_t n;

   if(list == NULL || *list == NULL) return NULL;

   for(i = 0, n = 0; list[i] != NULL; i++)
      n += strlen(list[i]) + 1;

   rv = fs_get(n*sizeof(char));
   *rv = '\0';
   for(i = 0; list[i] != NULL; i++){
      strcat(rv, list[i]);
      if(list[i+1] != NULL) strcat(rv, ",");
   }
   return rv;
}


char **array_to_list(char *array)
{
   int i;
   char *u;

   if(array == NULL || *array == '\0') return NULL;

   for(u = array, i = 0; u  && *u; u++)
       if(*u == ',') i++;

   return parse_list(array, i+1, 0,NULL);
}

char *
xoauth_config_line(XOAUTH2_INFO_S *x)
{
  size_t n;
  char *rv;

  if(x == NULL) return NULL;

  n = strlen(XNAME) + strlen((char *) x->name) + strlen(XID) + strlen(x->client_id)
	+ strlen(x->client_secret ? XSECRET : "") + strlen(x->client_secret ? x->client_secret : "")
	+ strlen(x->tenant ? XTENANT : "") + strlen(x->tenant ? x->tenant : "")
	+ strlen(XUSER) + strlen(x->users ? x->users : "")
	+ strlen(XFLOW) + strlen(x->flow ? x->flow : "")
	+ 2 + 3 + (x->client_secret ? 3 : 0) + (x->tenant ? 3 : 0)
	+ 3 + (x->flow ? 3 : 0) + 1;
  rv = fs_get(n*sizeof(char));
  sprintf(rv, "%s\"%s\" %s\"%s\"", XNAME, x->name, XID, x->client_id);
  if(x->client_secret)
     sprintf(rv + strlen(rv), " %s\"%s\"", XSECRET, x->client_secret);
  if(x->tenant)
     sprintf(rv + strlen(rv), " %s\"%s\"", XTENANT, x->tenant);
  sprintf(rv + strlen(rv), " %s\"%s\"", XUSER, x->users ? x->users : "");
  if(x->flow)
     sprintf(rv + strlen(rv), " %s\"%s\"", XFLOW, x->flow ? x->flow : "");
  return rv;
}

int
xoauth2_info_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
   int rv = 0;

   switch(cmd){
      case MC_CHOICE:
	*((*cl)->d.x.selected) = (*cl)->d.x.pat;
	rv = simple_exit_cmd(flags);

      case MC_EXIT:
	rv = simple_exit_cmd(flags);
	break;

      default:
	rv = -1;
   }

   if(rv > 0)
     ps->mangled_body = 1;

   return rv;
}

char *
xoauth2_extra_text(XOAUTH2_INFO_S **xinfo, int i)
{
  int methodcount;
  int j;
  XOAUTH2_INFO_S *x;
  char *rv = NULL;

  if(xinfo == NULL)
     return NULL;

  for(j = 0; xinfo[j] != NULL; j++);
  if(i < 0 || i >= j) return NULL;

  x = xinfo[i];
  if(x->flow){
    for(methodcount = 1, j = 0; xinfo[j] != NULL; j++)
      if(j != i && xinfo[j]->flow && !strucmp(xinfo[j]->flow, x->flow))
	methodcount++;
    if(methodcount == 1) return cpystr(x->flow);
  }

  /* we either do not have flow, or have more than one flow, use user */
  if(x->users){
      char **lval = array_to_list(xinfo[i]->users);
      if(lval && lval[0]){	/* use the first one */
	rv = cpystr(lval[0]);
	free_list_array(&lval);
      }
  }

  if(rv == NULL) rv = cpystr(_("client-id unused"));
  return rv;
}

XOAUTH2_INFO_S *
xoauth_info_choice(XOAUTH2_INFO_S **xinfo, char *user)
{
   int i, n, rv;
   char *extra;

   dprint((9, "xoauth_info_choice()"));

   for(i = 0; xinfo && xinfo[i]; i++);
   if(i == 0) return NULL;

   if(strucmp((char *) xinfo[0]->name, (char *) GMAIL_NAME) == 0){
      for(i = 0; xinfo && xinfo[i] && strcmp(xinfo[i]->client_id, (char *) GMAIL_ID) != 0; i++);
      if(!xinfo || xinfo[i] != NULL){
	int j, offset = 0;
	XOAUTH2_INFO_S *x;

	for(j = 0; xinfo[j] != NULL; j++){
	   if(j == i){
	      offset++;
	      continue;
	   }
	   x = xinfo[j] ? copy_xoauth2_info(xinfo[j]) : NULL;
	   free_xoauth2_info(&xinfo[j - offset]);
	   xinfo[j - offset] = x;
	}
	xinfo[j - offset] = NULL;
	for(i = 0; xinfo && xinfo[i]; i++);
      }
   }

   if(i == 1) return copy_xoauth2_info(xinfo[0]);

   if(!ps_global->ttyo){
	char *s;
	char prompt[1024];
	char reply[1024];
	int sel;
	for(i = n = 0; xinfo[i] != NULL; i++)
	   n += strlen(xinfo[i]->client_id) + strlen(xinfo[i]->flow ? xinfo[i]->flow : "")
		+ strlen(xinfo[i]->users ? xinfo[i]->users : "") + 8;	/* number, parenthesis, space */
	n += strlen((char *) xinfo[0]->name) + strlen(user);
	n += 1024;	/* large enough to display lines of 80 characters in UTF-8 */
	s = fs_get(n*sizeof(char));
	sprintf(s, _("Alpine cannot determine which client-id to use for the username <%s> for your %s account. "), user, xinfo[0]->name);
	sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
	for(i = 0; xinfo[i]; i++){
	   extra = xoauth2_extra_text(xinfo, i);
	   sprintf(s + strlen(s), " %d) %.70s%s%s\n", i+1, xinfo[i]->client_id,
				extra ? " - " : "", extra ? extra : "");
	   if(extra) fs_give((void **) &extra);
	}
	sprintf(s + strlen(s), "%s", "\n\n");

	display_init_err(s, 0);

	strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
	prompt[sizeof(prompt)-1] = '\0';

	do{
	   rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
	   sel = atoi(reply) - 1;
	   rv = (sel >= 0 && sel < i) ? 0 : -1;
	} while (rv != 0);
	return copy_xoauth2_info(xinfo[sel]);
   }
   else{
      CONF_S  *ctmp = NULL, *first_line = NULL;
      XOAUTH2_INFO_S *x_sel = NULL;
      OPT_SCREEN_S   screen;
      char tmp[1024];
      int final;

      ps_global->next_screen = SCREEN_FUN_NULL;

      memset(&screen, 0, sizeof(screen));


      final = (ps_global->ttyo && (ps_global->ttyo->screen_cols < sizeof(tmp))) ? ps_global->ttyo->screen_cols : sizeof(tmp) - 1;
      memset((void *) tmp, '-', final);
      tmp[final] = '\0';

      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT;
      ctmp->value = cpystr(tmp);

      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT;
      ctmp->value = cpystr(_("Select a Client-ID to use with this account"));

      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT;
      ctmp->value = cpystr(tmp);

      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT | CF_B_LINE;

      sprintf(tmp, _("Alpine cannot determine which client-id to use for the username <%s>"), user);
      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT;
      ctmp->value = cpystr(tmp);

      sprintf(tmp, _("for your %s account. Please select the client-id to use from the following list.\n\n"), xinfo[0]->name);
      new_confline(&ctmp);
      ctmp->flags |= CF_NOSELECT;
      ctmp->value = cpystr(tmp);

      for(i = 0; xinfo[i] != NULL; i++){
        new_confline(&ctmp);
        ctmp->flags |= CF_NOSELECT | CF_B_LINE;

	if((extra = xoauth2_extra_text(xinfo, i)) != NULL){
           new_confline(&ctmp);
           ctmp->flags |= CF_NOSELECT;
           ctmp->value = cpystr(extra);
	   ctmp->valoffset = 4;
	}

	new_confline(&ctmp);
        if(!first_line)
          first_line = ctmp;

	ctmp->value = cpystr(xinfo[i]->client_id);
        ctmp->d.x.selected = &x_sel;
        ctmp->d.x.pat      = copy_xoauth2_info(xinfo[i]);
        ctmp->keymenu      = &xoauth2_id_select_km;
        ctmp->help         = NO_HELP;
        ctmp->help_title   = NULL;
        ctmp->tool         = xoauth2_info_tool;
        ctmp->flags        = CF_STARTITEM;
        ctmp->valoffset    = 4 + (extra ? 3 : 0);
	if(extra) fs_give((void **) &extra);
      }
     (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT CLIENT_ID"),
                             _("xoauth2"), 0, NULL);
      return x_sel;
   }
   return NULL;
}

XOAUTH2_INFO_S **
parse_xoauth2_info_list(char **lval, int *totalp)
{
   int i, total;
   XOAUTH2_INFO_S **rv = NULL;

   for(total = 0; lval && lval[total]; total++);
   if(total){
      rv = fs_get((total + 1)*sizeof(XOAUTH2_INFO_S *));
      for(i = 0; i < total; i++)
	rv[i] = xoauth_parse_client_info(lval[i]);
      rv[i] = NULL;
   }
   *totalp = total;
   return rv;
}


XOAUTH2_INFO_S **
xoauth2_configured_servers(void)
{
  XOAUTH2_INFO_S **cv, **muv, **rv = NULL;
  int cvt, muvt, rvt, i, j, total = 0;

  cv = parse_xoauth2_info_list(ps_global->vars[V_XOAUTH2_INFO].current_val.l, &cvt);
  muv = parse_xoauth2_info_list(ps_global->vars[V_XOAUTH2_INFO].main_user_val.l, &muvt);

  rvt = cvt + muvt;
  if(rvt){
     rv = fs_get((rvt + 1)*sizeof(XOAUTH2_INFO_S *));
     memset((void *) rv, 0, (rvt + 1)*sizeof(XOAUTH2_INFO_S *));

     for(i = 0; cv && cv[i]; i++){
	for(j = 0; rv[j] && !same_xoauth2_info(*cv[i], *rv[j]); j++);
	if(!rv[j]) rv[total++] = copy_xoauth2_info(cv[i]);
     }
     free_xoauth2_info_list(&cv);

     for(i = 0; muv && muv[i]; i++){
	for(j = 0; rv[j] && !same_xoauth2_info(*muv[i], *rv[j]); j++);
	if(!rv[j]) rv[total++] = copy_xoauth2_info(muv[i]);
     }
     free_xoauth2_info_list(&muv);
  }
  return rv;
}

/* Get the client-id, etc. for server "name" associated to user "user" */
XOAUTH2_INFO_S *
oauth2_get_client_info(unsigned char *name, char *user)
{
  int i, j, matches, len;
  XOAUTH2_INFO_S *x, **xinfo, **lval;

  if(name == NULL || *name == '\0' || user == NULL || *user == '\0')
    return NULL;

  matches = 0;
  /* first count how many servers  */
  lval = xoauth2_configured_servers();
  for(i = 0; lval && lval[i]; i++){
     x = lval[i];
     if(x && x->name && name && !strcmp((char *) x->name, (char *) name))
	matches++;
  }

  /* if nothing, use the default value */
  for(i = 0; xoauth_default[i].name != NULL && strcmp((char *) xoauth_default[i].name, (char *) name); i++);
  if(xoauth_default[i].name) matches++;

  if(matches <= 1){
     free_xoauth2_info_list(&lval);
     return matches == 1 ? copy_xoauth2_info(&xoauth_default[i]) : NULL;
  }

  /* more than one match, see if it is a duplicate client-id entry */
  xinfo = fs_get((matches + 1)*sizeof(XOAUTH2_INFO_S *));
  memset((void *)xinfo, 0, (matches + 1)*sizeof(XOAUTH2_INFO_S *));
  matches = 0;	/* restart the recount, it might go lower! */
  for(i = 0; lval && lval[i]; i++){
     x = lval[i];
     if(x && x->name && name && !strcmp((char *) x->name, (char *) name)){
	for(j = 0; xinfo && xinfo[j] && !same_xoauth2_info(*x, *xinfo[j]); j++);
	if(!xinfo[j]) xinfo[matches++] = copy_xoauth2_info(x);
     }
  }
  for(i = 0; xoauth_default[i].name != NULL && strcmp((char *) xoauth_default[i].name, (char *) name); i++);
  for(j = 0; xinfo && xinfo[j] && !same_xoauth2_info(xoauth_default[i], *xinfo[j]); j++);
  if(!xinfo[j]) xinfo[matches++] = copy_xoauth2_info(&xoauth_default[i]);

  /* if after removing the duplicate entries, we only have one, use it */
  if(matches == 1){
     x = copy_xoauth2_info(xinfo[0]);
     free_xoauth2_info_list(&lval);
     free_xoauth2_info_list(&xinfo);
     return x;
  }

  x = NULL;	/* reset x to null so it will not be double freed */
  /* we have more than one match, now check if any of them matches the given user */
  matches = 0;
  for(i = 0; xinfo && xinfo[i]; i++){
      char **alval = array_to_list(xinfo[i]->users);
      for(j = 0; alval && alval[j] && strucmp(alval[j], user); j++);
      if(alval && alval[j]){
	 matches++;
	 free_xoauth2_info(&x);
	 x = copy_xoauth2_info(xinfo[i]);
      }
      if(alval) free_list_array(&alval);
  }

  /* only one server matches the username */
  if(matches == 1){
     free_xoauth2_info_list(&xinfo);
     free_xoauth2_info_list(&lval);
     return x;
  }

  free_xoauth2_info(&x);

  /* We either have no matches, or have more than one match!
   * in either case, let the user pick what they want */
   x = xoauth_info_choice(xinfo, user);
   free_xoauth2_info_list(&xinfo);
   free_xoauth2_info_list(&lval);

   /* Once the user chose a client-id, save it so we do not ask again */
   if(x != NULL){
      int n = x->users ? strlen(x->users) + 1 : 0;
      char ***alval, **lvalp;

      fs_resize((void **) &x->users, (n + strlen(user) + 1)*sizeof(char));
      x->users[n > 0 ? n - 1 : 0] = '\0';
      if(n > 0) strcat(x->users, ",");
      strcat(x->users, user);
      alval = ALVAL(&ps_global->vars[V_XOAUTH2_INFO], Main);
      lvalp = *alval;

      for(n = 0; lvalp && lvalp[n]; n++);
      fs_resize((void **) &lvalp, (n+2)*sizeof(char *));
      lvalp[n] = xoauth_config_line(x);
      lvalp[n+1] = NULL;
      *alval = xoauth2_conf_dedup_and_merge(&lvalp);
      set_current_val(&ps_global->vars[V_XOAUTH2_INFO], FALSE, FALSE);
      write_pinerc(ps_global, Main, WRP_NONE);
   }

   return x;
}

/* write vlist to v 
 * Each vlist member is of type "p", while "v" is of type "l", so we
 * each entry in "l" by using each of the "p" entries.
 */
void
write_xoauth_configuration(struct variable  *v, struct variable **vlist, EditWhich ew)
{
  int i, k, m, n;
  XOAUTH2_INFO_S *x = NULL;
  char ***alval, **lval, **l;
  char *p;

  for (i = 0, n = 0; vlist[i] != NULL; i++)	/* count number of lines we need */
      if(!strcmp(vlist[i]->name, XOAUTH2_USERS))
	n++;
  lval = fs_get((n+1)*sizeof(char *));
  memset((void *) lval, 0, (n+1)*sizeof(char *));

  m = -1;
  alval  = ALVAL(v, ew);
  for (i = 0, k = 0; vlist[i] != NULL; i++){
      if(x == NULL){
	 x = new_xoauth2_info();
	 x->name = (unsigned char *) cpystr(vlist[i]->descrip);	/* hack! but makes life so much easier! */
	 for(m = 0; xoauth_default[m].name != NULL
		    && strcmp((char *) xoauth_default[m].name, (char *) x->name); m++);
      }
      if (x->client_id == NULL && !strcmp(vlist[i]->name, XOAUTH2_CLIENT_ID)){
	 p = PVAL(vlist[i], ew);
	 if (p == NULL) p = vlist[i]->current_val.p;
	 if(p != NULL)
	    x->client_id = cpystr(p);
	 continue;
      }
      if (x->client_secret == NULL
	  && m >= 0
	  && xoauth_default[m].client_secret
	  && !strcmp(vlist[i]->name, XOAUTH2_CLIENT_SECRET)){
	 p = PVAL(vlist[i], ew);
	 if (p == NULL) p = vlist[i]->current_val.p;
	 if(p != NULL)
	    x->client_secret = cpystr(p);
	 continue;
      }
      if (x->tenant == NULL
	  && m >= 0
	  && xoauth_default[m].tenant
	  && !strcmp(vlist[i]->name, XOAUTH2_TENANT)){
	 p = PVAL(vlist[i], ew);
	 if (p == NULL) p = vlist[i]->current_val.p;
	 if(p != NULL)
	    x->tenant = cpystr(p);
	 continue;
      }
      if (x->flow == NULL && !strcmp(vlist[i]->name, XOAUTH2_FLOW)){
	 p = PVAL(vlist[i], ew);
	 if (p == NULL) p = vlist[i]->current_val.p;
	 if(p != NULL)
	    x->flow = cpystr(p);
	 continue;
      }
      if (x->users == NULL && !strcmp(vlist[i]->name, XOAUTH2_USERS)){
	 l = LVAL(vlist[i], ew);
	 x->users = list_to_array(l);
      }
      /* don't let it get to here until we are done! */
      lval[k++] = xoauth_config_line(x);
	/* get ready for next run */
      free_xoauth2_info(&x);
      m = -1;
  }
  if(k > 0){
     lval[k] = NULL;
     if(*alval) free_list_array(alval);
     *alval = lval;
  }
  else
     *alval = NULL;
  set_current_val(&ps_global->vars[V_XOAUTH2_INFO], FALSE, FALSE);
}


/* parse line of the form
  /NAME="text" /ID="text" /TENANT="text" /SECRET="text" /USER="text"
 */
XOAUTH2_INFO_S *
xoauth_parse_client_info(char *lvalp)
{
  char *s, *t, c;
  XOAUTH2_INFO_S *x;

  if (lvalp == NULL) return NULL;

  x = new_xoauth2_info();
  if((s = strstr(lvalp, XNAME)) != NULL){
	s += strlen(XNAME);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if (*s) x->name = (unsigned char *) cpystr(s);
	*t = c;
  } else x->name = NULL;

  if((s = strstr(lvalp, XID)) != NULL){
	s += strlen(XID);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if (*s) x->client_id = cpystr(s);
	*t = c;
  } else x->client_id = NULL;

  if((s = strstr(lvalp, XTENANT)) != NULL){
	s += strlen(XTENANT);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if (*s) x->tenant = cpystr(s);
	*t = c;
  } else x->tenant = NULL;

  if((s = strstr(lvalp, XSECRET)) != NULL){
	s += strlen(XSECRET);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if (*s) x->client_secret = cpystr(s);
	*t = c;
  } else x->client_secret = NULL;

  if((s = strstr(lvalp, XFLOW)) != NULL){
	s += strlen(XFLOW);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if(*s)	x->flow = cpystr(s);
	*t = c;
  } else x->flow = NULL;

  if((s = strstr(lvalp, XUSER)) != NULL){
	s += strlen(XUSER);
	if(*s == '"') s++;
	for(t = s; *t && *t != '"' && *t != ' '; t++);
	c = *t;
	*t = '\0';
	if(*s)	x->users = cpystr(s);
	*t = c;
  } else x->users = NULL;

  return x;
}

char **
xoauth2_conf_dedup_and_merge(char ***alval)
{
   int i, j, l, m;
   char **lval, **rv;
   XOAUTH2_INFO_S *x, *y;

   if(alval == NULL || *alval == NULL || **alval == NULL)
      return NULL;

   lval = *alval;
   for(i = 0; lval[i] != NULL; i++);	/* count how many entries */

   rv = fs_get((i+1)*sizeof(char *));
   memset((void *)rv, 0, (i+1)*sizeof(char *));

   for (i = 0;  lval[i] != NULL; i++){
       x = xoauth_parse_client_info(lval[i]);
       for (j = 0; rv[j] != NULL; j++){
           y = xoauth_parse_client_info(rv[j]);
	   /* check if this is the same data. If so, merge the users into one and discard the old data */
	   if(same_xoauth2_info(*x, *y)){
	      char **l1, **l2, **l3;
	      int k, n;
	      /* merge user1 with user2, save in x->users */
	      l1 = array_to_list(x->users);
	      l2 = array_to_list(y->users);

	      for(n = 0; l1 && l1[n]; n++);
	      for(m = 0; l2 && l2[m]; m++, n++);
	      l3 = fs_get((n+1)*sizeof(char*));
	      memset((void *) l3, 0, (n+1)*sizeof(char *));

	      for(l = 0, n = 0; l1 && l1[l]; l++){
		  for(k = 0; l1 && l1[j] && l3[k] && strucmp(l1[l], l3[k]); k++);
		  if(l3[k] == NULL && l1 && l1[l] != NULL)
		     l3[n++] = cpystr(l1[l]);
	      }
	      for(l = 0; l2 && l2[l]; l++){
		  for(k = 0; l2 && l2[l] && l3[k] && strucmp(l2[l], l3[k]); k++);
		  if(l3[k] == NULL && l2 && l2[l] != NULL)
		     l3[n++] = cpystr(l2[l]);
	      }
	      l3[n++] = NULL;
	      if(x->users) fs_give((void **) &x->users);
	      x->users = list_to_array(l3);
	      fs_give((void **) &rv[j]);
	      rv[j] = xoauth_config_line(x);

	      if(l1) free_list_array(&l1);
	      if(l2) free_list_array(&l2);
	      if(l3) free_list_array(&l3);
	      free_xoauth2_info(&y);
	      break;
	   }
	   free_xoauth2_info(&y);
       }
       if(rv[j] == NULL) rv[j] = cpystr(lval[i]);
   }
   return rv;
}

/*
 * X = new value, Y = default configuration
 */
void
write_xoauth_conf_entry(XOAUTH2_INFO_S *x, XOAUTH2_INFO_S *y, CONF_S **cl, CONF_S **clb, CONF_S **fline,
	struct variable ***varlistp, int *pp, int ln, int key)
{
   CONF_S *ctmpb = *clb;
   struct variable **varlist;
   int p = *pp;
   char tmp[1024], tmp2[16];

   sprintf(tmp2, "%d", key);
   varlist = *varlistp;

   new_confline(cl)->var = NULL;
   if(fline && !*fline) *fline = *cl;
   (*cl)->flags    |= CF_NOSELECT;
   (*cl)->help      = NO_HELP;
   (*cl)->valoffset = 1;
   (*cl)->value     = cpystr((char *) x->name);
   (*cl)->varname   = NULL;
   (*cl)->varnamep  = ctmpb = *cl;

   /* Setup client-id variable */
   varlist[p] = fs_get(sizeof(struct variable));
   memset((void *) varlist[p], 0, sizeof(struct variable));
   varlist[p]->name = cpystr(XOAUTH2_CLIENT_ID);
   varlist[p]->is_used = 1;
   varlist[p]->is_user = 1;
   varlist[p]->main_user_val.p = x->client_id && y->client_id
		&& strcmp(x->client_id, y->client_id) ? cpystr(x->client_id) : NULL;
   varlist[p]->global_val.p = y->client_id ? cpystr(y->client_id) : NULL;
   varlist[p]->dname   = cpystr(tmp2);		/* hack, but makes life easier! */
   varlist[p]->descrip = cpystr((char *) x->name);	/* hack, but makes life easier! */
   set_current_val(varlist[p], FALSE, FALSE);

   /* Write client-id variable */
   new_confline(cl)->var = varlist[p];
   utf8_snprintf(tmp, sizeof(tmp), "   %-*.100w =", ln, XOAUTH2_CLIENT_ID);
   tmp[sizeof(tmp)-1] = '\0';
   (*cl)->varname   = cpystr(tmp);
   (*cl)->varmem    = p++;
   (*cl)->valoffset = ln + 3 + 3;
   (*cl)->value     = pretty_value(ps_global, *cl);
   (*cl)->keymenu   = &config_xoauth2_text_keymenu;
   (*cl)->help      = h_config_xoauth2_client_id;
   (*cl)->tool      = text_tool;
   (*cl)->varnamep  = ctmpb;

   /* Set up client-secret variable */
   if(x->client_secret){
     varlist[p] = fs_get(sizeof(struct variable));
     memset((void *) varlist[p], 0, sizeof(struct variable));
     varlist[p]->name = cpystr(XOAUTH2_CLIENT_SECRET);
     varlist[p]->is_used = 1;
     varlist[p]->is_user = 1;
     varlist[p]->main_user_val.p = y->client_secret
		&& strcmp(x->client_secret, y->client_secret)
		? cpystr(x->client_secret) : NULL;
     varlist[p]->global_val.p = y->client_secret ? cpystr(y->client_secret) : NULL;
     varlist[p]->dname   = cpystr(tmp2);	/* hack, but makes life easier! */
     varlist[p]->descrip = cpystr((char *) x->name);	/* hack, but makes life easier! */
     set_current_val(varlist[p], FALSE, FALSE);

     /* Write client-secret variable */
     new_confline(cl)->var = varlist[p];
     utf8_snprintf(tmp, sizeof(tmp), "   %-*.100w =", ln, XOAUTH2_CLIENT_SECRET);
     tmp[sizeof(tmp)-1] = '\0';
     (*cl)->varname   = cpystr(tmp);
     (*cl)->varmem    = p++;
     (*cl)->valoffset = ln + 3 + 3;
     (*cl)->value     = pretty_value(ps_global, *cl);
     (*cl)->keymenu   = &config_xoauth2_text_keymenu;
     (*cl)->help      = h_config_xoauth2_client_secret;
     (*cl)->tool      = text_tool;
     (*cl)->varnamep  = ctmpb;
   }

   /* Set up tenant variable */
   if(x->tenant){
     varlist[p] = fs_get(sizeof(struct variable));
     memset((void *) varlist[p], 0, sizeof(struct variable));
     varlist[p]->name = cpystr(XOAUTH2_TENANT);
     varlist[p]->is_used = 1;
     varlist[p]->is_user = 1;
     varlist[p]->main_user_val.p = y->tenant && strcmp(x->tenant, y->tenant)
		? cpystr(x->tenant) : NULL;
     varlist[p]->global_val.p = y->tenant ? cpystr(y->tenant) : NULL;
     varlist[p]->dname   = cpystr(tmp2);	/* hack, but makes life easier! */
     varlist[p]->descrip = cpystr((char *) x->name);	/* hack, but makes life easier! */
     set_current_val(varlist[p], FALSE, FALSE);

     /* Write client-secret variable */
     new_confline(cl)->var = varlist[p];
     utf8_snprintf(tmp, sizeof(tmp), "   %-*.100w =", ln, XOAUTH2_TENANT);
     tmp[sizeof(tmp)-1] = '\0';
     (*cl)->varname   = cpystr(tmp);
     (*cl)->varmem    = p++;
     (*cl)->valoffset = ln + 3 + 3;
     (*cl)->value     = pretty_value(ps_global, *cl);
     (*cl)->keymenu   = &config_xoauth2_text_keymenu;
     (*cl)->help      = h_config_xoauth2_tenant;
     (*cl)->tool      = text_tool;
     (*cl)->varnamep  = ctmpb;
   }

   /* Set up flow variable */
   if(x->flow){
     varlist[p] = fs_get(sizeof(struct variable));
     memset((void *) varlist[p], 0, sizeof(struct variable));
     varlist[p]->name = cpystr(XOAUTH2_FLOW);
     varlist[p]->is_used = 1;
     varlist[p]->is_user = 1;
     varlist[p]->main_user_val.p = cpystr(x->flow);
     varlist[p]->global_val.p = cpystr(x->flow);
     varlist[p]->dname   = cpystr(tmp2);	/* hack, but makes life easier! */
     varlist[p]->descrip = cpystr((char *) x->name);	/* hack, but makes life easier! */
     set_current_val(varlist[p], FALSE, FALSE);

     /* Write client-secret variable */
     new_confline(cl)->var = varlist[p];
     utf8_snprintf(tmp, sizeof(tmp), "   %-*.100w =", ln, XOAUTH2_FLOW);
     tmp[sizeof(tmp)-1] = '\0';
     (*cl)->varname   = cpystr(tmp);
     (*cl)->varmem    = p++;
     (*cl)->valoffset = ln + 3 + 3;
     (*cl)->value     = pretty_value(ps_global, *cl);
     (*cl)->keymenu   = &config_xoauth2_text_keymenu;
     (*cl)->help      = h_config_xoauth2_flow;
     (*cl)->tool      = text_tool;
     (*cl)->varnamep  = ctmpb;
   }

   /* Setup users variable */
   varlist[p] = fs_get(sizeof(struct variable));
   memset((void *) varlist[p], 0, sizeof(struct variable));
   varlist[p]->name = cpystr(XOAUTH2_USERS);
   varlist[p]->is_used = 1;
   varlist[p]->is_user = 1;
   varlist[p]->is_list = 1;
   varlist[p]->main_user_val.l = x->users ? array_to_list(x->users) : NULL;
   varlist[p]->dname   = cpystr(tmp2);		/* hack, but makes life easier! */
   varlist[p]->descrip = cpystr((char *) x->name);	/* hack, but makes life easier! */
   set_current_val(varlist[p], FALSE, FALSE);

   /* Write user variable */
   new_confline(cl)->var = varlist[p];
   utf8_snprintf(tmp, sizeof(tmp), "   %-*.100w =", ln, XOAUTH2_USERS);
   tmp[sizeof(tmp)-1] = '\0';
   (*cl)->varname   = cpystr(tmp);
   (*cl)->valoffset = ln + 3 + 3;
   (*cl)->keymenu   = &config_xoauth2_wshuf_keymenu;
   (*cl)->help      = h_config_xoauth2_username;
   if(x->users){
      int z;
      for(z = 0; varlist[p]->main_user_val.l[z]; z++){
	if(z) new_confline(cl);
	(*cl)->var       = varlist[p];
	(*cl)->varmem    = z;
	(*cl)->valoffset = ln + 3 + 3;
	(*cl)->value     = pretty_value(ps_global, *cl);
	(*cl)->keymenu   = &config_xoauth2_wshuf_keymenu;
	(*cl)->tool      = text_tool;
	(*cl)->varnamep  = ctmpb = *cl;
      }
   }
   else {
	(*cl)->varmem = 0;
	(*cl)->value = pretty_value(ps_global, *cl);
	(*cl)->keymenu = &config_xoauth2_wshuf_keymenu;
	(*cl)->tool    = text_tool;
	(*cl)->varnamep  = ctmpb = *cl;
   }
   p++;
   *pp = p;
   *varlistp = varlist;
   *clb = ctmpb;

   /* Separate servers with a blank line */
   new_confline(cl);
   (*cl)->flags    |= CF_NOSELECT | CF_B_LINE;
}

/*----------------------------------------------------------------------
  Screen to add client_id and client_secret for a service

  ---*/
void
alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
{
    struct variable **varlist = NULL;
    char	    **lval, ***alval;
    int		    i, j, k, p, ln = 0, readonly_warning = 0, pos, count_vars;
    XTYPES	    m;
    CONF_S	   *ctmpa = NULL, *ctmpb, *first_line;
    PINERC_S       *prc = NULL;
    OPT_SCREEN_S    screen;
    int             expose_hidden_config;
    SAVED_CONFIG_S *vsave;
    XOAUTH2_INFO_S  *y;

    dprint((3, "--  alpine_xoauth2_configuration --\n"));

    expose_hidden_config = F_ON(F_EXPOSE_HIDDEN_CONFIG, ps_global);
    ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;

    if(ps->restricted)
      readonly_warning = 1;
    else{
	switch(ew){
	  case Main:
	    prc = ps->prc;
	    break;
	  case Post:
	    prc = ps->post_prc;
	    break;
	  default:
	    break;
	}

	readonly_warning = prc ? prc->readonly : 1;
    }

    ps->next_screen = SCREEN_FUN_NULL;

    mailcap_free(); /* free resources we won't be using for a while */

    pos = -1;
    for(ln = 0, m = Xid; m < Xend; m++){
	i = strlen(x_default[m].screen_name);
	if(ln < i) ln = i;
    }

    alval = ALVAL(&ps->vars[V_XOAUTH2_INFO], ew);
    lval = *alval = xoauth2_conf_dedup_and_merge(alval);
    set_current_val(&ps_global->vars[V_XOAUTH2_INFO], FALSE, FALSE);
    write_pinerc(ps_global, ew, WRP_NONE);

    do {
        ctmpa = first_line = NULL;

        for(i = 0, count_vars = 0; xoauth_default[i].name != NULL; i++){
	   /* always start with the default configuration */
	   for(k = 0; lval && lval[k]; k++){
		y = xoauth_parse_client_info(lval[k]);
		if(same_xoauth2_info(xoauth_default[i], *y))
		   break;
		free_xoauth2_info(&y);
	   }
	   if(lval == NULL || lval[k] == NULL){
		count_vars += 3;
		if(xoauth_default[i].client_secret) count_vars++;
		if(xoauth_default[i].tenant) count_vars++;
	   }
	   for(k = 0; lval && lval[k]; k++){
	      y = xoauth_parse_client_info(lval[k]);
	      if(y && (!y->name || strcmp((char *) y->name, (char *) xoauth_default[i].name))){
		free_xoauth2_info(&y);
		continue;
	      }
	      count_vars += 3;
	      if(xoauth_default[i].client_secret != NULL) count_vars++;
	      if(xoauth_default[i].tenant != NULL) count_vars++;
	      free_xoauth2_info(&y);
	   }
	}

	for(i = 0; varlist && varlist[i]; i++){
	    free_variable_values(varlist[i]);
	    if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
	    if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
	    fs_give((void **) &varlist[i]);
	}
	if(varlist) fs_give((void **) varlist);

	varlist = fs_get((count_vars + 1)*sizeof(struct variable *));
	memset((void *) varlist, 0, (count_vars +1)*sizeof(struct variable *));

        for(i = 0, p = 0; xoauth_default[i].name != NULL; i++){
	   /* always start with the default configuration */
	   for(k = 0; lval && lval[k]; k++){
		y = xoauth_parse_client_info(lval[k]);
		if(same_xoauth2_info(xoauth_default[i], *y))
		   break;
		free_xoauth2_info(&y);
	   }
	   if(lval == NULL || lval[k] == NULL){
	       OAUTH2_S *oa2list;
	       for(oa2list = alpine_oauth2_list; oa2list && oa2list->name; oa2list++){
		  if(oa2list->hide) continue;
		  if(!strcmp((char *) oa2list->name, (char *) xoauth_default[i].name)){
		     xoauth_default[i].flow = cpystr(oa2list->server_mthd[0].name ? "Authorize"
                      : (oa2list->server_mthd[1].name ? "Device" : "Unknown"));
		     write_xoauth_conf_entry(&xoauth_default[i], &xoauth_default[i], &ctmpa, &ctmpb,
						&first_line, &varlist, &p, ln, -i-1);
		     fs_give((void **) &xoauth_default[i].flow);
		     break;	/* just one entry, set the default to the first entry */
		  }
	       }
	   }
	   for(k = 0; lval && lval[k]; k++){
	      OAUTH2_S *oa2list, *oa2;

	      y = xoauth_parse_client_info(lval[k]);
	      if(y && (!y->name || strcmp((char *) y->name, (char *) xoauth_default[i].name))){
		free_xoauth2_info(&y);
		continue;
	      }
	      if(y->client_id == NULL)
		 y->client_id = cpystr(xoauth_default[i].client_id);
	      if(y->client_secret == NULL && xoauth_default[i].client_secret != NULL)
		 y->client_secret = cpystr(xoauth_default[i].client_secret);
	      if(y->tenant == NULL && xoauth_default[i].tenant != NULL)
		 y->tenant = cpystr(xoauth_default[i].tenant);
	      for(oa2 = NULL, oa2list = alpine_oauth2_list; oa2 == NULL && oa2list; oa2list++)
		 if(!strcmp((char *) oa2list->name, (char *) y->name)) oa2 = oa2list;
	      if(oa2 && y->flow == NULL)
		y->flow = cpystr(oa2->server_mthd[0].name ? "Authorize"
                      : (oa2->server_mthd[1].name ? "Device" : "Unknown"));
	      if(oa2 && !oa2->hide)
		write_xoauth_conf_entry(y, &xoauth_default[i], &ctmpa, &ctmpb, &first_line, &varlist, &p, ln, k);
	      free_xoauth2_info(&y);
	   }
	}

	vsave = save_config_vars(ps, expose_hidden_config);
	first_line = pos < 0 ? first_sel_confline(first_line) : set_confline_number(first_line, pos);
	pos = -1;
	memset(&screen, 0, sizeof(screen));
	screen.ro_warning = readonly_warning;
	/* TRANSLATORS: Print something1 using something2.
	"configuration" is something1 */
	switch(conf_scroll_screen(ps, &screen, first_line, "XOAUTH2 Alpine Info",
				      _("configuration"), 0, &pos)){
	      case 0:
		break;

	      case 1:
		write_xoauth_configuration(&ps->vars[V_XOAUTH2_INFO], varlist, ew);
		write_pinerc(ps, ew, WRP_NONE);
		break;

	      case 4:				/* add a service */
		{char service[MAILTMPLEN+1];
		 char prompt[MAILTMPLEN+1];
		 int flags = OE_DISALLOW_HELP;
		 strncpy(prompt, _("Enter service name: "), sizeof(prompt));
		 prompt[sizeof(prompt) - 1] = '\0';
		 service[0] = '\0';
		 if(optionally_enter(service,
                        -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
                         0, sizeof(service), prompt, NULL, NO_HELP, &flags) == 0){
		    for(i = 0; xoauth_default[i].name != NULL && strucmp((char *) xoauth_default[i].name, service); i++);
		    if(xoauth_default[i].name == NULL)
			q_status_message1(SM_ORDER, 3, 3, _("Service %s not known"), service);
		    else{
			char **list;
			ClearScreen();
			ps_global->mangled_screen = 1;
			for(j = 0; lval && lval[j]; j++);
			list = fs_get((j+2)*sizeof(char *));
			memset((void *)list, 0, (j+2)*sizeof(char *));
			list[0] = xoauth_config_line(&xoauth_default[i]);
			for(i = 0; lval && lval[i]; i++)
			   list[i+1] = cpystr(lval[i]);
			if(lval) free_list_array(&lval);
			*alval = lval = list;
			for(i = 0; varlist && varlist[i]; i++){
			   free_variable_values(varlist[i]);
			   if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
			   if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
			   fs_give((void **) &varlist[i]);
			}
			if(varlist) fs_give((void **) varlist);
		    }
		 }
		}
		break;

	      case 5:				/* delete a service */
		{ int m, key;
		  XOAUTH2_INFO_S *x;
		  char question[MAILTMPLEN];

		   for(i = 0, m = 1, j = 0; varlist[i] && m < pos;)
			if(!varlist[i]->is_list){
			   i++; m++;
			} else {
			    if(varlist[i]
				&& varlist[i]->current_val.l
				&& varlist[i]->current_val.l[j++])
				m++;
			    else{
				j = 0; m += 2; i++;
			    }
			}
		   key = atoi(varlist[i]->dname);	/* this hack avoids we rebuild varlist again */
		   if(key >= 0){
			x = xoauth_parse_client_info(lval[key]);
			snprintf(question, sizeof(question), _("Delete this configuration for %s "), x->name);
			free_xoauth2_info(&x);
			if(want_to(question, 'n', 'n', NO_HELP, WT_NORM) != 'y')
			   break;
		        for(i = key; lval && lval[i] && lval[i+1]; i++){
			   fs_give((void **) &lval[i]);
			   lval[i] = cpystr(lval[i+1]);
			}
			fs_give((void **) &lval[i]);
		   }
		   else {
			q_status_message(SM_ORDER, 3, 3, _("Cannot delete default configuration"));
			break;
		   }
		   if(lval && lval[0] == NULL)
		     free_list_array(&lval);
		   *alval = lval;
		   pos = 1;	/* reset at the top */
		   for(i = 0; varlist && varlist[i]; i++){
		       free_variable_values(varlist[i]);
		       if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
		       if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
		       fs_give((void **) &varlist[i]);
		   }
		   if(varlist) fs_give((void **) varlist);
		}
		break;

	      case 10:
		revert_to_saved_config(ps, vsave, expose_hidden_config);
		if(prc)
		  prc->outstanding_pinerc_changes = 0;
		break;
      
	      default:
		q_status_message(SM_ORDER,7,10,
		    "conf_scroll_screen bad ret, not supposed to happen");
		break;
	}
    } while (pos >= 0);

#ifdef _WINDOWS
    mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global));
#endif
}
