/*
 * MU-Conference - Multi-User Conference Service
 * Copyright (c) 2002-2005 David Sutton
 *
 *
 * This program is free software; you can redistribute it and/or drvify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
 */

#include "conference.h"

void con_get_role_list(gpointer key, gpointer data, gpointer arg)
{
  xmlnode node;
  xmlnode result;
  taffil affiliation;
  trole role;
  jid userid;
  cnr room;

  result = (xmlnode)arg;

  if(result == NULL)
  {
    log_warn(NAME, "[%s] Aborting: NULL result - <%s>", FZONE, (char*) key);
    return;
  }

  room = (cnr)xmlnode_get_vattrib(result ,"cnr");

  if(room == NULL)
  {
    log_warn(NAME, "[%s] Aborting: NULL room - <%s>", FZONE, (char*) key);
    return;
  }

  node = xmlnode_insert_tag(result, "item");
  userid = jid_new(xmlnode_pool(node), key);

  xmlnode_put_attrib(node, "jid", key);

  affiliation = affiliation_level(room, userid);
  role = role_level(room, userid);

  xmlnode_put_attrib(node, "role", role.msg);
  xmlnode_put_attrib(node, "affiliation", affiliation.msg);
}

void con_get_affiliate_list(gpointer key, gpointer data, gpointer arg)
{
  xmlnode node;
  cnr room;
  taffil affiliation;
  jid userid;
  char *actor;
  char *reason;


  xmlnode result = (xmlnode)arg;
  xmlnode item = (xmlnode)data; 

  if(result == NULL || item == NULL)
  {
    log_warn(NAME, "[%s] Aborting: NULL attribute(s) - <%s>", FZONE, (char *) key);
    return;
  }

  actor = xmlnode_get_attrib(item, "actor");
  reason = xmlnode_get_data(item);
  room = (cnr)xmlnode_get_vattrib(result ,"cnr");

  node = xmlnode_insert_tag(result, "item");

  userid = jid_new(xmlnode_pool(node), key);

  xmlnode_put_attrib(node, "jid", key);

  affiliation = affiliation_level(room, userid);

  xmlnode_put_attrib(node, "affiliation", affiliation.msg);

  if(reason != NULL)
    xmlnode_insert_cdata(xmlnode_insert_tag(node, "reason"), reason, -1);

  if(actor != NULL)
    xmlnode_insert_cdata(xmlnode_insert_tag(node, "actor"), actor, -1);
}


/* Generate x:data form for configuring lists */
xmlnode con_gen_list(cnr room, char *namespace, char *type)
{
  xmlnode result;

  result = xmlnode_new_tag("query");
  xmlnode_put_attrib(result,"xmlns", namespace);

  if (room == NULL)
  {
    log_warn(NAME, "[%s] NULL room attribute", FZONE);
    return result;
  }

  xmlnode_put_vattrib(result, "cnr", (void*)room);

  if(j_strcmp(type, "owner") == 0)
    g_hash_table_foreach(room->owner, con_get_affiliate_list, (void*)result);
  else if(j_strcmp(type, "admin") == 0)
    g_hash_table_foreach(room->admin, con_get_affiliate_list, (void*)result);
  else if(j_strcmp(type, "moderator") == 0)
    g_hash_table_foreach(room->moderator, con_get_role_list, (void*)result);
  else if(j_strcmp(type, "member") == 0)
  {
    log_debug(NAME, "[%s] member list size [%d]", FZONE, g_hash_table_size(room->member));
    g_hash_table_foreach(room->member, con_get_affiliate_list, (void*)result);
  }
  else if(j_strcmp(type, "participant") == 0)
    g_hash_table_foreach(room->participant, con_get_role_list, (void*)result);
  else if(j_strcmp(type, "outcast") == 0)
    g_hash_table_foreach(room->outcast, con_get_affiliate_list, (void*)result);

  xmlnode_hide_attrib(result, "cnr");

  return result;
}

void adm_user_kick(cnu user, cnu target, char *reason)
{
  cnr room;
  xmlnode data;
  xmlnode pres;
  const char *status;

  if(user == NULL || target == NULL || reason == NULL)
  {
    log_warn(NAME, "[%s] Aborting: NULL attribute found", FZONE);
    return;
  }

  room = target->room;

  data = xmlnode_new_tag("reason");

  if(is_outcast(room, target->realid))
    status = STATUS_MUC_BANNED;
  else
    status = STATUS_MUC_KICKED;

  xmlnode_put_attrib(data, "status", status);
  xmlnode_put_attrib(data, "actor", jid_full(jid_user(user->realid)));
  xmlnode_put_attrib(data, "actnick", user->localid->resource);
  xmlnode_insert_cdata(data, reason, -1);

  pres = jutil_presnew(JPACKET__UNAVAILABLE, jid_full(target->realid), NULL);
  target->presence = pres;
  log_debug(NAME, "[%s] Kick/Ban requested. Status code=%s", FZONE, status);

  con_send_alert(target, reason, NULL, status);
  con_user_zap(target, data);

  return;
}

void con_parse_item(cnu sender, jpacket jp)
{
  xmlnode current;
  xmlnode result;
  xmlnode node;
  jid target;
  jid from;
  char *xmlns;
  char *role;
  char *jabberid;
  char *nick;
  char *reason;
  char *affiliation;
  cnu user;
  cnr room;

  int error = 0;

  if(sender == NULL)
  {
    log_warn(NAME, "[%s] Aborting - NULL sender", FZONE);
    return;
  }

  user = NULL;
  from = sender->realid;
  room = sender->room;
  node = xmlnode_get_tag(jp->x, "query");
  xmlns = xmlnode_get_attrib(node, "xmlns");

  /* Check for configuration request */
  if(j_strcmp(xmlns, NS_MUC_OWNER) == 0 && xmlnode_get_tag(node, "item") == NULL)
  {
    if(is_owner(room, from))
    {
      user = g_hash_table_lookup(room->remote, jid_full(from));
      if(user)
      {
        xdata_room_config(room, user, room->locked, jp->x);
        xmlnode_free(jp->x);
      }
      else
      {
        jutil_error(jp->x, TERROR_BAD);
        deliver(dpacket_new(jp->x), NULL);
      }
      return;
    }
    else
    {
      jutil_error(jp->x, TERROR_NOTALLOWED);
      deliver(dpacket_new(jp->x), NULL);
      return;
    }
  }

  /* Parse request for errors */
  for(current = xmlnode_get_firstchild(node); current != NULL; current = xmlnode_get_nextsibling(current))
  {
    if (xmlnode_get_name(current)==NULL) continue;
    /* Extract data */
    jabberid = xmlnode_get_attrib(current, "jid");
    nick = xmlnode_get_attrib(current, "nick");
    role = xmlnode_get_attrib(current, "role");
    affiliation = xmlnode_get_attrib(current, "affiliation");
    reason = xmlnode_get_tag_data(current, "reason");

    if(jabberid == NULL && nick == NULL && role == NULL && affiliation == NULL)
    {
      error = 1;
      log_debug(NAME, "[%s] Skipping - Badly formed request (%s)", FZONE, xmlnode2str(current));
      insert_item_error(current, "400", "Badly formed request");
      continue;
    }

    if(jpacket_subtype(jp) == JPACKET__GET)
    {
      if(jabberid == NULL && nick == NULL)
      {
        if(!is_admin(room, from))
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - Insufficent level to request admin list", FZONE);
          insert_item_error(jp->x, "403", "Forbidden list retrieval");
          continue;
        }

        if(role != NULL && affiliation != NULL)
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - Badly formed request (%s)", FZONE, xmlnode2str(current));
          insert_item_error(jp->x, "400", "Badly formatted list request");
          continue;
        }

        if(j_strcmp(affiliation, "admin") != 0 && j_strcmp(role, "participant") != 0 && j_strcmp(affiliation, "member") != 0 && j_strcmp(role, "moderator") != 0 && j_strcmp(affiliation, "outcast") != 0 && j_strcmp(affiliation, "owner") != 0)
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - No such list (%s)", FZONE, xmlnode2str(current));
          insert_item_error(jp->x, "400", "No such list");
          continue;
        }

        if(j_strcmp(affiliation, "admin") == 0 || j_strcmp(affiliation, "owner") == 0)
        {
          if(j_strcmp(xmlns, NS_MUC_ADMIN) != 0)
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - Badly formed namespace : %s (%s)", FZONE, xmlns,xmlnode2str(current));
            insert_item_error(jp->x, "400", "Invalid Namespace");
            continue;
          }

          if(!is_owner(room, from))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - Insufficent level to request %s list", FZONE, affiliation);
            insert_item_error(jp->x, "403", "Forbidden list retrieval");
            continue;
          }
        }
        else if(j_strcmp(xmlns, NS_MUC_ADMIN) != 0)
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - Badly formed namespace (%s)", FZONE, xmlnode2str(current));
          insert_item_error(jp->x, "400", "Invalid Namespace");
          continue;
        }
      }
      else
      {
        error = 1;
        log_debug(NAME, "[%s] Skipping - Badly formed request (%s)", FZONE, xmlnode2str(current));
        insert_item_error(jp->x, "400", "Badly formed request - extra attributes found");
        continue;
      }
    }
    else if(jpacket_subtype(jp) == JPACKET__SET)
    {
      if(role == NULL && affiliation == NULL)
      {
        error = 1;
        log_debug(NAME, "[%s] Skipping - no role or affiliation given (%s)", FZONE, xmlnode2str(current));
        insert_item_error(jp->x, "400", "Badly formed request - no role or affiliation attribute");
        continue;
      }

      if(nick == NULL && jabberid == NULL)
      {
        error = 1;
        log_debug(NAME, "[%s] Skipping - no jid or nick given (%s)", FZONE, xmlnode2str(current));
        insert_item_error(jp->x, "400", "Badly formed request - no nick or jid attribute");
        continue;
      }

      if(jabberid == NULL)
      {
        user = g_hash_table_lookup(room->local, nick);

        if(user)
        {
          jabberid = jid_full(user->realid);
        }
        else
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - can't find jid (%s)", FZONE, xmlnode2str(current));
          insert_item_error(jp->x, "400", "Nick not present in room");
          continue;
        }
      }

      target = jid_new(jp->p, jabberid);

      if((target == NULL || target->user == NULL) && role != NULL)
      {
        error = 1;
        log_debug(NAME, "[%s] Skipping - Bad jid (%s)", FZONE, jabberid);
        insert_item_error(jp->x, "400", "Badly formed JID");
        continue;
      }

      if(role != NULL && affiliation != NULL)
      {
        /* Check requesting user has minimum affiliation level */
        error = 1;
        log_debug(NAME, "[%s] Skipping - Attempting to change role and affiliation (%s)", FZONE, jabberid);
        insert_item_error(jp->x, "400", "Bad request - trying to change role and affiliation");
        continue;
      }

      /* Affiliation changes */
      if(affiliation != NULL)
      {
        if(!is_admin(room, from))
        { 
          /* Check requesting user has minimum affiliation level */
          error = 1;
          log_debug(NAME, "[%s] Skipping - affiliation role requested by non-admin(%s)", FZONE, jabberid);
          insert_item_error(jp->x, "403", "Forbidden - No affiliation requested by non-admin");
          continue;
        }
        else if(!is_owner(room, from) && is_admin(room, target))
        {  
          /* Stop admins altering other admins */
          error = 1;
          log_debug(NAME, "[%s] Skipping - affiliation role requested by non-admin(%s)", FZONE, jabberid);
          insert_item_error(jp->x, "403", "Forbidden - No affiliation request between admins");
          continue;
        }
        else if(j_strcmp(affiliation, "owner") == 0)
        {
          if(!is_owner(room, from))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - affiliation role requested by non-owner(%s)", FZONE, jabberid);
            insert_item_error(jp->x, "403", "Forbidden - Owner requested");
            continue;
          }
        }
        else if(j_strcmp(affiliation, "admin") == 0)
        { 
          if(!is_owner(room, from))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - affiliation role requested by non-owner(%s)", FZONE, jabberid);
            insert_item_error(jp->x, "403", "Forbidden - Admin requested");
            continue;
          }
        }
        else if(j_strcmp(affiliation, "outcast") == 0)
        { 
          if(is_admin(room, target))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - affiliation role requested by non-owner(%s)", FZONE, jabberid);
            insert_item_error(jp->x, "403", "Forbidden - Admin requested");
            continue;
          }
        }
	else if ((j_strcmp(affiliation, "owner") != 0) && (is_owner(room, target)) && (g_hash_table_size(room->owner) == 1))
        {
          //trying to remove the last owner
          error = 1;
          log_debug(NAME, "[%s] Skipping - room %s must have at least one owner", FZONE, room->name);
          insert_item_error(jp->x, "409", "Conflict - Can't remove last admin");
          continue;
        }
        else if(j_strcmp(affiliation, "member") != 0 && j_strcmp(affiliation, "none") != 0)
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - affiliation unknown(%s/%s)", FZONE, jabberid, affiliation);
          insert_item_error(jp->x, "400", "Unknown affiliation");
          continue;
        }
      }

      /* role changes */
      if(role != NULL)
      {

        if(!is_admin(room, from) && !is_moderator(room, from))
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - Forbidden role change (%s)", FZONE, jabberid);
          insert_item_error(jp->x, "403", "Forbidden role change request by non-admin");
          continue;
        }
        else if(j_strcmp(role, "moderator") == 0)
        {
          if(!is_admin(room, from))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - Forbidden moderater request (%s)", FZONE, jabberid);
            insert_item_error(jp->x, "403", "Forbidden moderator request by non-admin");
            continue;
          }
        }
        else if(j_strcmp(role, "none") == 0)
        {
          if(is_admin(room, target) || is_moderator(room, target))
          {
            error = 1;
            log_debug(NAME, "[%s] Skipping - Forbidden kick request (%s)", FZONE, jabberid);
            insert_item_error(jp->x, "403", "Forbidden Kick request against admin");
            continue;
          }
        }
        else if(j_strcmp(role, "participant") != 0 && j_strcmp(role, "visitor") != 0)
        {
          error = 1;
          log_debug(NAME, "[%s] Skipping - role unknown(%s)", FZONE, jabberid);
          insert_item_error(jp->x, "400", "Unknown role");
          continue;
        }
      }
    } 

    log_debug(NAME, "[%s] Ok (%s)", FZONE, xmlnode2str(current));
  }

  /* If theres an error, return */
  if(error == 1)
  {
    jutil_iqresult(jp->x);
    xmlnode_put_attrib(jp->x, "type", "error");

    xmlnode_insert_node(jp->x, node);

    deliver(dpacket_new(jp->x), NULL);
    return;
  }

  /* Now process the checked results */
  result = xmlnode_new_tag("query");
  xmlnode_put_attrib(result, "xmlns", xmlns);

  for(current = xmlnode_get_firstchild(node); current != NULL; current = xmlnode_get_nextsibling(current))
  {
    if (xmlnode_get_name(current)==NULL) continue;
    jabberid = xmlnode_get_attrib(current, "jid");
    nick = xmlnode_get_attrib(current, "nick");
    role = xmlnode_get_attrib(current, "role");
    affiliation = xmlnode_get_attrib(current, "affiliation");
    reason = xmlnode_get_tag_data(current, "reason");


    if(jpacket_subtype(jp) == JPACKET__GET)
    {
      if(role != NULL)
        result = con_gen_list(room, xmlns, role);
      else
        result = con_gen_list(room, xmlns, affiliation);
      break;
    }
    else
    {
      /* Find jabberid for this user */
      if(jabberid == NULL)
      {
        user = g_hash_table_lookup(room->local, nick);
        jabberid = jid_full(user->realid);
      }
      else if(user == NULL)
      {
        user = g_hash_table_lookup(room->remote, jabberid);
      }

      /* Convert jabberid into a jid struct */	
      target = jid_new(jp->p, jabberid);

      if(role == NULL && affiliation != NULL)
      {
        log_debug(NAME, "[%s] Requesting affiliation change for %s to (%s)", FZONE, jabberid, affiliation);

        change_affiliate(affiliation, sender, target, reason, from);

        if(target != NULL && target->user != NULL)
        {
          if(j_strcmp(affiliation, "owner") == 0)
          {
            change_role("moderator", sender, target, reason);
          }
          else if(j_strcmp(affiliation, "admin") == 0)
          {
            change_role("moderator", sender, target, reason);
          }
          else if(j_strcmp(affiliation, "outcast") == 0)
          {
            change_role("none", sender, target, reason);
          }
          else
          {
            change_role("participant", sender, target, reason);
          }
        }
      }
      else if(role != NULL && affiliation == NULL)
      {
        log_debug(NAME, "[%s] Requesting role change for %s to (%s)", FZONE, jabberid, role);
        change_role(role, sender, target, reason);
      }
      else
      {
        log_debug(NAME, "[%s] Request: role %s, affiliation %s, for %s", FZONE, role, affiliation, jabberid);
      }
    }
  }

  jutil_iqresult(jp->x);

  if(result)
  {
    xmlnode_insert_node(jp->x, result);
    xmlnode_free(result);
  }

  deliver(dpacket_new(jp->x), NULL);
  return;
}

