#include <config.h>
#include <stdio.h>
#include "IMAccept.hh"
#include "IIIMP_IMState.hh"
#include "IIIMP_ICState.hh"
#include "IIIMProtocol.hh"
#include "IIIMPTrans.hh"
#include "IIIMPUtil.hh"

bool
IIIMP_IMState::parse_user_name(
    const u16string& uname,
    u16string& username,
    u16string& password,
    string& hostname
)
{
    int pos_at, pos_sharp;
    u16string hname_u16;

    pos_at = uname.find('@');
    if (pos_at == u16string::npos) {
	hname_u16 = "";
	pos_sharp = uname.find('#');
	if (pos_sharp == u16string::npos) {
	    username = uname;
	    password = "";
	} else {
	    username = uname.substr(0, pos_sharp);
	    password = uname.substr(pos_sharp + 1);
	}
    } else {
	username = uname.substr(0, pos_at);
	pos_sharp = uname.find('#', pos_at);
	if (pos_sharp == u16string::npos) {
	    hname_u16 = uname.substr(pos_at + 1);
	    password = "";
	} else {
	    hname_u16 = uname.substr(pos_at + 1, pos_sharp - pos_at - 1);
	    password = uname.substr(pos_sharp + 1);
	}
    }
    if (!hname_u16.get_charstr()) return false;
    hostname = hname_u16.get_charstr();

    return true;
}

bool
IIIMP_IMState::send(
    IIIMP_message *pmes,
    bool deletep
)
{
    if (!pmes) return false;
    // TODO...
    pimt->send(pmes);
    if (deletep)
	iiimp_message_delete(pimt->get_data_s(), pmes);
    return true;
}

bool
IIIMP_IMState::send_protocol_version(
    int client_proto_version
)
{
    if (client_proto_version > get_current_protocol_version())
	client_proto_version = get_current_protocol_version();

    // For version 2 clients or older ones,
    // we must not send IM_PROTOCOL_VERSION.
    if (client_proto_version <= 2)
	return true;

    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    if (!send(iiimp_protocol_version_new(pdata_s, get_im_id(), client_proto_version),
	      true))
	return false;

    return true;
}

bool
IIIMP_IMState::send_trigger_keys()
{
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IIIMP_keyevent *keys;
    IIIMP_keyevent_list *pion;
    IIIMP_keyevent_list *pioff;
    IMKeySpecList onkeys;
    IMKeySpecList offkeys;
    IMKeySpecList::iterator key;
    int i, size;

    get_imhandler()->get_triggerkeys(onkeys, offkeys);

    size = onkeys.size();
    keys = new IIIMP_keyevent[size];
    for (i = 0, key = onkeys.begin(); key != onkeys.end(); key++, i++) {
	keys[i].keycode = key->get_keycode();
	keys[i].keychar = key->get_keychar();
	keys[i].modifier = key->get_modifier();
	keys[i].time_stamp = key->get_timestamp();
    }
    pion = iiimp_keyevent_list_new(pdata_s, size, keys);
    delete[] keys;

    size = offkeys.size();
    keys = new IIIMP_keyevent[size];
    for (i = 0, key = offkeys.begin(); key != offkeys.end(); key++, i++) {
	keys[i].keycode = key->get_keycode();
	keys[i].keychar = key->get_keychar();
	keys[i].modifier = key->get_modifier();
	keys[i].time_stamp = key->get_timestamp();
    }
    pioff = iiimp_keyevent_list_new(pdata_s, size, keys);
    delete[] keys;

    if (!pion || !pioff) {
	if (pion) iiimp_keyevent_list_delete(pdata_s, pion);
	if (pioff) iiimp_keyevent_list_delete(pdata_s, pioff);
	return false;
    }

    if (!send(iiimp_register_trigger_keys_new(pdata_s, get_im_id(), pion, pioff),
	      true))
	return false;

    return true;
}

bool
IIIMP_IMState::send_hotkeys()
{
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    HOTKEY_LIST *hklist;
    IIIMP_card16 profile_id;
    int num_profiles = get_hotkey_profile()->get_num_profiles();
    IIIMP_card16 scope_and_profile_id;

    for (profile_id = IIIMP_PROFILE_ID_FIRST; profile_id <= num_profiles; profile_id++) {
	hklist = get_hotkey_profile()->retrive_hotkeys(pdata_s, profile_id);
        scope_and_profile_id = (GLOBAL_HOTKEY << 15) | profile_id;
	if (!send(iiimp_register_hotkeys_new(pdata_s, get_im_id(), scope_and_profile_id, hklist), true))
	    return false;
        scope_and_profile_id = 0;
    }
    return true;
}

bool
IIIMP_IMState::switch_hotkey_profile()
{
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IIIMP_card16 scope_and_profile_id;
    IIIMP_card16 profile_id = IIIMP_PROFILE_ID_FIRST;

    scope_and_profile_id = (GLOBAL_HOTKEY << 15) | profile_id;
    if (!send(iiimp_select_hotkey_profile_new(pdata_s, get_im_id(), scope_and_profile_id), true))
      return false;
    return true;
}

bool
IIIMP_IMState::message_proc(
    void *x_pmes
)
{
    IIIMP_message *pmes = (IIIMP_message*) x_pmes;
    IMState *pimns;

    switch (pmes->opcode) {
      case IM_CONNECT:
      {
	  IMAccept *pima = get_imaccept();
	  IMHandler *pimh;
	  const IMLangList *planglist;
	  u16string username, password;
	  string hostname;
	  int proto_version = pmes->v.connect.protocol_version;

	  if (!parse_user_name(CONV_IIIMP_STR(pmes->v.connect.user_name),
			       username,
			       password,
			       hostname))
	      return false;
	  pimh = pima->request_connect(pimt->get_fd(),
				       username,
				       hostname,
				       password,
				       IMAuthList());
	  if (!pimh) break;

	  // Authentication is completed.  Now start communication!
	  send_protocol_version(proto_version);

	  planglist = pimh->get_langlist();

	  {
	      IIIMP_string *phead, *pcur, *pstr;
	      IMLangList::const_iterator it;
	      u16string langid;

	      phead = NULL;
	      for (it = planglist->begin(); it != planglist->end(); it++) {
		  langid = it->get_id();
		  pstr = iiimp_string_new(pimt->get_data_s(),
					  langid.size(),
					  langid.data());
		  // memory error
		  if (!pstr) break;
		  if (!phead) phead = pstr;
		  else pcur->next = pstr;
		  pcur = pstr;
	      }
	      if (!send(iiimp_connect_reply_new(pimt->get_data_s(),
						get_im_id(),
						phead),
			true))
		  return false;
	  }
	  
	  pimns = new IIIMP_IMState_Identified(this, pimh, proto_version);

	  send_trigger_keys();

	  send_hotkeys();

          switch_hotkey_profile();
 
	  change_state(*pimns);

	  break;
      }
      default:
       LOG_ERROR("Invalid opcode:%d.  (Authentication is requied.)", pmes->opcode);
       return false;
    }
    return true;
}

void
IIIMP_IMState::destroy()
{
    delete get_iiimptrans();
    IMState::destroy();
}

IIIMP_IMState::IIIMP_IMState(
    IIIMProtocol *pimp,
    IIIMPTrans *x_pimt,
    int proto_version
) :
    IMState(1, pimp, NULL, proto_version)
{
    pimt = x_pimt;
}

IIIMP_imattribute *
IIIMP_IMState_Identified::
create_object_descriptors()
{
    const IMObjectWithDescList *podlist = get_imhandler()->get_imobjectdesclist();

    if (!podlist) return NULL;

    IIIMP_imattribute *pima = NULL;
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IIIMP_string *pidomain, *pihrn, *pisig, *piscope;
    IIIMP_object_descriptor *piodh, *piod, *piod2;
    IIIMP_card16 predefined;

    IMObjectWithDescList::const_iterator it;
    IMObjectWithDesc *pod;

    piodh = NULL;
    for (it = podlist->begin(); it != podlist->end(); it++) {
	pod = *it;
	if (pod->get_category() < 0) continue;
	predefined = convert_IMObject_type_to_iiimp_predefined_attribid(pod->get_type());

	pidomain = pihrn = pisig = piscope = NULL;

	pidomain = iiimp_string_new(pdata_s,
				    pod->get_domain().size(),
				    pod->get_domain().data());
	pihrn = iiimp_string_new(pdata_s,
				 pod->get_hrn().size(),
				 pod->get_hrn().data());
	pisig = iiimp_string_new(pdata_s,
				 pod->get_signature().size(),
				 pod->get_signature().data());
	piscope = iiimp_string_new(pdata_s,
				   pod->get_scope().size(),
				   pod->get_scope().data());

	if (!pidomain || !pihrn || !pisig || !piscope) {
	    goto memory_error;
	}
	piod2 = iiimp_object_descriptor_new(pdata_s,
					    pod->get_category(),
					    pod->get_object_size(),
					    predefined, pod->get_attribid(),
					    pidomain, pihrn, pisig, piscope);
	if (!piod2) goto memory_error;

	if (!piodh) {
	    piodh = piod2;
	} else {
	    piod->next = piod2;
	}
	piod = piod2;
    }

    if (!piodh) return NULL;

    pima = iiimp_imattribute_object_descriptor_new(pdata_s,
						   IIIMP_IMATTRIBUTE_OBJECT_DESCRIPTOR_LIST,
						   0, piodh);
    if (!pima) goto memory_error;

    return pima;

memory_error:
    if (pima) {
	iiimp_imattribute_delete(pdata_s, pima);
    } else {
	if (pidomain) iiimp_string_delete(pdata_s, pidomain);
	if (pihrn) iiimp_string_delete(pdata_s, pihrn);
	if (pisig) iiimp_string_delete(pdata_s, pisig);
	if (piscope) iiimp_string_delete(pdata_s, piscope);
	for (piod = piodh; piod; piod = piod->next) {
	    iiimp_object_descriptor_delete(pdata_s, piod);
	}
    }
    // TODO!! we have to throw an exception.
    return NULL;
}

IIIMP_language*
IIIMP_IMState_Identified::
create_language_list(
    const IMLangList *plangs
)
{
    IIIMP_language *pil, *pil2, *pilh;
    IIIMP_string *pihrn, *piid;
    u16string langid;
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IMLangList::const_iterator it;

    if (!plangs) return NULL;

    pilh = NULL;
    for (it = plangs->begin(); it != plangs->end(); it++) {
	pihrn = piid = NULL;

	pihrn = iiimp_string_new(pdata_s,
				 it->get_hrn().size(),
				 it->get_hrn().data());
	if (!pihrn) goto memory_error;

	langid = it->get_id();
	piid = iiimp_string_new(pdata_s,
				langid.size(),
				langid.data());
	if (!piid) {
	    iiimp_string_delete(pdata_s, pihrn);
	    goto memory_error;
	}

	pil2 = iiimp_language_new(pdata_s, pihrn, piid);
	
	if (!pil2) {
	    iiimp_string_delete(pdata_s, pihrn);
	    iiimp_string_delete(pdata_s, piid);
	    goto memory_error;
	}

	if (!pilh) {
	    pilh = pil2;
	} else {
	    pil->next = pil2;
	}
	pil = pil2;
    }

    return pilh;

memory_error:
    if (pilh) iiimp_language_delete(pdata_s, pilh);
    // TODO!! we have to throw an exception.
    return NULL;
}

IIIMP_imattribute*
IIIMP_IMState_Identified::
create_input_method_descriptors()
{
    const IMDescriptorList *pimdlist = get_imhandler()->get_imdesclist();

    if (!pimdlist) return NULL;

    IIIMP_imattribute *pima = NULL;
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IIIMP_string *pidname, *pihrn, *pidomain;
    IIIMP_inputmethod_descriptor *pimdh, *pimd, *pimd2;
    IIIMP_language *pil;

    IMDescriptorList::const_iterator it;

    pimdh = NULL;

    for (it = pimdlist->begin(); it != pimdlist->end(); it++) {
	pidname = pihrn = NULL;

	pidname = iiimp_string_new(pdata_s,
				   it->get_imname().size(),
				   it->get_imname().data());

	pihrn = iiimp_string_new(pdata_s,
				 it->get_hrn().size(),
				 it->get_hrn().data());

	/* Currently adopt an empty string
	   as reverse domain name. */
	pidomain = iiimp_string_new(pdata_s, 0, NULL);

	if (!pidname || !pihrn || !pidomain) {
	    goto memory_error;
	}

	pil = create_language_list(it->get_languages());
	pimd2 = iiimp_inputmethod_descriptor_new(pdata_s,
						 it->get_attribid(),
						 pidname,
						 pihrn,
						 pil,
						 pidomain);

	if (!pimd2) goto memory_error;

	if (!pimdh) {
	    pimdh = pimd2;
	} else {
	    pimd->next = pimd2;
	}
	pimd = pimd2;
    }
    if (!pimdh) return NULL;

    pima = iiimp_imattribute_inputmethod_descriptor_new(pdata_s,
							IIIMP_IMATTRIBUTE_INPUT_METHOD_LIST,
							0, pimdh);
    if (!pima) goto memory_error;

    return pima;

memory_error:
    if (pima) {
	iiimp_imattribute_delete(pdata_s, pima);
    } else {
	if (pidname) iiimp_string_delete(pdata_s, pidname);
	if (pihrn) iiimp_string_delete(pdata_s, pihrn);
	if (pidomain) iiimp_string_delete(pdata_s, pidomain);
	for (pimd = pimdh; pimd; pimd = pimd->next) {
	    iiimp_inputmethod_descriptor_delete(pdata_s, pimd);
	}
    }
    // TODO!! we have to throw an exception.
    return NULL;
}

bool
IIIMP_IMState_Identified::
set_data_to_client()
{
    IIIMP_imattribute *pima, *pima_objdesc, *pima_imdesc;

    pima_objdesc = create_object_descriptors();

    if (check_protocol_version(3))
	/* Send it only to clients of version 3 or later. */
	pima_imdesc = create_input_method_descriptors();
    else
	pima_imdesc = NULL;

    if (pima_objdesc) {
	pima = pima_objdesc;
	pima->next = pima_imdesc;
    } else {
	pima = pima_imdesc;
    }

    if (!send(iiimp_setimvalues_new(get_iiimptrans()->get_data_s(),
				    get_im_id(), pima), true))
	return false;
    return true;
}

IIIMP_imattribute*
IIIMP_IMState_Identified::get_imattribute(
    IIIMP_card16 id
)
{
    IIIMP_imattribute *pimattr;
    IIIMP_data_s *pdata_s = get_iiimptrans()->get_data_s();
    IMObject *pobj = IMObjectMgr::get_instance()->get_object_from_attribid(id);
    if (!pobj || !pobj->downloadablep()) return NULL;
    switch (pobj->get_type()) {
      case IMObject::BINARY_GUI:
      case IMObject::BINARY_LWE:
      {
	  IIIMP_binaryfile_object *pbobj;
	  IIIMP_string *pipath;
	  IMObjectWithDesc *pod = (IMObjectWithDesc*) pobj;

	  pipath = iiimp_string_new(pdata_s,
				    pod->get_path().size(),
				    pod->get_path().data());
	  if (!pipath) goto memory_error;
	  pbobj = iiimp_binaryfile_object_new(pdata_s, pipath);
	  if (!pbobj) {
	      iiimp_string_delete(pdata_s, pipath);
	      goto memory_error;
	  }
	  if (pobj->get_type() == IMObject::BINARY_GUI) {
	      pimattr = iiimp_imattribute_binary_gui_object_new(pdata_s,
								id, id,
								pbobj);
	  } else {
	      pimattr = iiimp_imattribute_binary_light_weight_engine_new(pdata_s,
									 id, id,
									 pbobj);
	  }
	  if (!pimattr) {
	      iiimp_binaryfile_object_delete(pdata_s, pbobj);
	      goto memory_error;
	  }
      }
      break;
      case IMObject::JAVA_GUI:
      case IMObject::JAVA_LWE:
      {
	  IIIMP_jarfile_object *pjobj;
	  IIIMP_string *piclassnamesh, *piclassnames, *piclassnames2;
	  IMObjectWithDesc *pod = (IMObjectWithDesc*) pobj;

	  const CARD8BIT *pbr = pod->get_binary_representation();
	  if (!pbr) return NULL;

	  piclassnamesh = NULL;
	  const list<u16string> &classes = pod->get_classes();
	  list<u16string>::const_iterator it;
	  for (it = classes.begin(); it != classes.end(); it++) {
	      piclassnames2 = iiimp_string_new(pdata_s,
					       it->size(),
					       it->data());
	      if (!piclassnames2) {
		  if (piclassnamesh)
		      iiimp_string_delete(pdata_s, piclassnamesh);
		  goto memory_error;
	      }
	      if (!piclassnamesh) {
		  piclassnamesh = piclassnames2;
	      } else {
		  piclassnames->next = piclassnames2;
	      }
	      piclassnames = piclassnames2;
	  }
	  pjobj = iiimp_jarfile_object_new(pdata_s, piclassnamesh,
					   pod->get_object_size(),
					   pbr);
	  if (!pjobj) {
	      iiimp_string_delete(pdata_s, piclassnamesh);
	      goto memory_error;
	  }
	  if (pobj->get_type() == IMObject::JAVA_GUI) {
	      pimattr = iiimp_imattribute_jar_gui_object_new(pdata_s, id, id, pjobj);
	  } else {
	      pimattr = iiimp_imattribute_jar_light_weight_engine_object_new(pdata_s,
									     id, id,
									     pjobj);
	  }
	  if (!pimattr) {
	      iiimp_jarfile_object_delete(pdata_s, pjobj);
	      goto memory_error;
	  }
      }
      break;

      default:
       return NULL;
    }

    return pimattr;

memory_error:
    // TODO: throw exception.
    return NULL;
}

bool
IIIMP_IMState_Identified::message_proc(
    void *message
)
{
    IIIMP_message *pmes = (IIIMP_message*) message;
    IMState *pimns;
    switch (pmes->opcode) {
      case IM_GETIMVALUES:
      {
	  IIIMP_imattribute *pimattrh, *pimattr, *pimattr2;
	  IIIMP_card16_list *pcl = pmes->v.getimvalues.attr_list;
	  IIIMP_card16 *ptr = pcl->ptr;
	  pimattrh = NULL;
	  for (int i = 0; i < pcl->count; i++, ptr++) {
	      pimattr2 = get_imattribute(*ptr);
	      if (!pimattr2) continue;
	      if (!pimattrh) {
		  pimattrh = pimattr2;
	      } else {
		  pimattr->next = pimattr2;
	      }
	      pimattr = pimattr2;
	  }
	  if (!send(iiimp_getimvalues_reply_new(get_iiimptrans()->get_data_s(),
						get_im_id(),
						pimattrh),
		    true))
	      return false;
	  break;
      }
      break;
      case IM_SETIMVALUES:
      {
	  IIIMP_imattribute *pima = pmes->v.setimvalues.attr_list;
	  switch(pima->id) {
	    case IIIMP_IMATTRIBUTE_CLIENT_DESCRIPTOR:
	    {
		IIIMP_client_descriptor *pcd = pima->value.client_descriptor;

		IMHandler::ClientAttrList attrs;
		if (pcd->type) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::CLIENT_NAME,
							  CONV_IIIMP_STR(pcd->type)));
		}
		if (pcd->os_name) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::OS_NAME,
							  CONV_IIIMP_STR(pcd->os_name)));
		}
		if (pcd->arch) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::OS_ARCH,
							  CONV_IIIMP_STR(pcd->arch)));
		}
		if (pcd->version) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::OS_VERSION,
							  CONV_IIIMP_STR(pcd->version)));
		}
		if (pcd->X_display_name) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::X_DISPLAY_NAME,
							  CONV_IIIMP_STR(pcd->X_display_name)));
		}
		if (pcd->X_server_vendor) {
		    attrs.push_back(IMHandler::ClientAttr(IMHandler::X_SERVER_VENDOR,
							  CONV_IIIMP_STR(pcd->X_server_vendor)));
		}

		if (!(get_imhandler()->set_client_info(attrs)))
		    return false;

		set_data_to_client();

		if (!send(iiimp_setimvalues_reply_new(get_iiimptrans()->get_data_s(),
						      get_im_id()),
			  true))
		    return false;
		break;
	    }

	    default:
	     LOG_ERROR("Unknown attribid:%d.", pima->id);
	     return false;
	  }
      }
      break;

      case IM_CREATEIC:
      {
	  IIIMP_icattribute *pattr = pmes->v.createic.attr_list;
	  ICAttribute icattr = convert_iiimp_icattr(pattr);
	  ICHandler *pich = get_imhandler()->createic(icattr);
	  if (!pich) {
	      LOG_ERROR("Fail to create new ic (ICHandler).");
	      return false;
	  }
	  CARD16BIT ic_id;
	  if (!new_ic_id(ic_id)) return false;
	  IIIMP_ICState *pics = IIIMP_ICState::create(ic_id, this, pich);
	  if (!pics) {
	      pich->destroy(NULL);
	      LOG_ERROR("Fail to create new ic (IIIMP_ICState).");
	      return false;
	  }
	  add_icstate(pics);

	  if (!send(iiimp_createic_reply_new(get_iiimptrans()->get_data_s(),
					     get_im_id(),
					     ic_id),
		    true))
	      return false;
      }
      break;

      case IM_DISCONNECT:
      {
	  // reset_state() will delete `this'.
	  bool flag;
	  CARD16BIT im_id = get_im_id();
	  IIIMPTrans *ptrans = get_iiimptrans();

	  cleanup_ic();
	  
	  flag = send(iiimp_disconnect_reply_new(ptrans->get_data_s(),
						 im_id),
		      true);
	  reset_state();
	  if (!flag) return false;
      }
      break;

      case IM_SETIMVALUES_REPLY:
       // reply message.  Currently, simply ignore it.
       break;

      default:
       LOG_ERROR("Invalid opcode:%d.  (Authentication is requied.)", pmes->opcode);
       return false;
    }
    return true;
}

IIIMP_IMState_Identified::IIIMP_IMState_Identified(
    IIIMP_IMState *pimt,
    IMHandler *pimh,
    int proto_version
) : IIIMP_IMState(*pimt, proto_version)
{
    set_imhandler(pimh);
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
