text: /*
    Name:             appint
    Copyright:        Copyright (C) SIL International.
    Documentation:
    Description:
    Create Date:      5 Nov 2007
  
    Modified Date:    23 Jun 2014
    Authors:          mcdurdin
    Related Files:
    Dependencies:
  
    Bugs:
    Todo:
    Notes:
    History:          05 Nov 2007 - mcdurdin - I1129 - Fix irregular behaviour with context rules in debugger
                      27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration
                      11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
                      12 Mar 2010 - mcdurdin - I934 - x64 - Complete
                      12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
                      03 Oct 2011 - mcdurdin - I3091 - IMX DLLs cause an assertion failure due to buffer size mismatch
                      17 Nov 2012 - mcdurdin - I3575 - V9.0 - context must be cached for legacy mode in TSF as rules are processed twice
                      10 Jun 2014 - mcdurdin - I4262 - V9.0 - TSF deadkeys do not function correctly
                      23 Jun 2014 - mcdurdin - I4287 - V9.0 - Remove extraneous AppIntegration class type tests
  */
     // I4287
  #include "pch.h"
  
  PWSTR decxstr(PWSTR p, PWSTR pStart);
  
  const LPSTR ItemTypes[8] = {
  	"QIT_VKEYDOWN", "QIT_VKEYUP", "QIT_VSHIFTDOWN", "QIT_VSHIFTUP",
      "QIT_CHAR", "QIT_DEADKEY", "QIT_BELL", "QIT_BACK" };
  
  /* AppContext */
  
  AppContext::AppContext()
  {
  	Reset();
  }
  
  void AppContext::Add(WCHAR ch)
  {
  	if(pos == MAXCONTEXT - 1) {
  //    SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: MAXCONTEXT[%d]: %ws", pos, CurContext);
      auto p = incxstr(CurContext);
      auto n = p - CurContext;
      memmove(CurContext, p, (MAXCONTEXT - n) * 2);
      pos -= (int)n;
  	}
  
  	CurContext[pos++] = ch;
  	CurContext[pos] = 0;
  
    SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Add(%x) [%d]: %s", ch, pos, Debug_UnicodeString(CurContext));
  }
  
  WCHAR *AppContext::Buf(int n)
  {
  	WCHAR *p;
  
  	//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Buf(%d)", n);
  	//if(n == 0) return wcschr(CurContext, 0);
  	//if(*CurContext == 0) return NULL;
  
  	for(p = wcschr(CurContext, 0); p != NULL && n > 0 && p > CurContext; p = decxstr(p, CurContext), n--);
  	//for(p = wcschr(CurContext, 0); n > 0 && p > CurContext; p--, n--);
  
  	if(n > 0) return NULL;
  	return p;
  }
  
  WCHAR *AppContext::BufMax(int n)
  {
  	WCHAR *p = wcschr(CurContext, 0);  // I3091
  
  	if(CurContext == p || n == 0) return p; /* empty context or 0 characters requested, return pointer to end of context */  // I3091
  
    WCHAR *q = p;  // I3091
  	for(; p != NULL && p > CurContext && (INT_PTR)(q-p) < n; p = decxstr(p, CurContext));  // I3091
  
    if((INT_PTR)(q-p) > n) p = incxstr(p); /* Copes with deadkey or supplementary pair at start of returned buffer making it too long */  // I3091
  
    return p;  // I3091
  }
  
  void AppContext::Delete()
  {
    if (CharIsDeadkey()) {
      pos -= 2;
    } else if (CharIsSurrogatePair()) {
      pos--;
    }
  	//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Delete");
  
  	if(pos > 0) pos--;
  	CurContext[pos] = 0;
  	//if(--pos < 0) pos = 0;
  	//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Delete");
  }
  
  void AppContext::Reset()
  {
  	pos = 0;
  	CurContext[0] = 0;
  
  //	SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Reset");
  }
  
  void AppContext::Get(WCHAR *buf, int bufsize)
  {
    // surrogate pairs need to be treated as a single unit, therefore use
    // BufMax to find a start index.
    // BufMax handles the case where a surrogate pair at the
    // start of the buffer is split by bufsize
    for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--)
    {
      *buf = *p;
      if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) {
        buf++; p++;
        *buf = *p;
        bufsize--;
      }
      buf++;
    }
  
    *buf = 0;
  }
  
  void AppContext::CopyFrom(AppContext *source)   // I3575
  {
    SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 0));
    wcscpy_s(CurContext, _countof(CurContext), source->CurContext);
  	pos = source->pos;
  }
  
  
  void AppContext::Set(const WCHAR *buf)
  {
    const WCHAR *p;
    WCHAR *q;
  
    // We may be past a buffer longer than our internal
    // buffer. So we shift to make sure we capture the end
    // of the string, not the start
    p = wcschr(buf, 0);
    q = (WCHAR *)p;
    while (p != NULL && p > buf && (intptr_t)(q - p) < MAXCONTEXT - 1) {
      p = decxstr((WCHAR *)p, (WCHAR *)buf);
    }
  
    // If the first character in the buffer is a surrogate pair,
    // or a deadkey, our buffer may be too long, so move to the
    // next character in the buffer
    if ((intptr_t)(q - p) > MAXCONTEXT - 1) {
      p = incxstr((WCHAR *)p);
    }
  
    for (q = CurContext; *p; p++, q++) {
      *q = *p;
    }
  
    *q = 0;
    pos = (int)(intptr_t)(q - CurContext);
    CurContext[MAXCONTEXT - 1] = 0;
  
  }
  
  BOOL AppContext::CharIsDeadkey()
  {
  	if(pos < 3) // code_sentinel, deadkey, #, 0
  		return FALSE;
  	return CurContext[pos-3] == UC_SENTINEL &&
  		   CurContext[pos-2] == CODE_DEADKEY;
  }
  
  BOOL AppContext::CharIsSurrogatePair()
  {
    if (pos < 2) // low_surrogate, high_surrogate
      return FALSE;
  
    return Uni_IsSurrogate1(CurContext[pos - 2]) &&
      Uni_IsSurrogate2(CurContext[pos - 1]);
  }
  
  BOOL AppContext::IsEmpty() {
    return (BOOL)(pos == 0);
  }
  
  /* AppActionQueue */
  
  AppActionQueue::AppActionQueue()
  {
    memset(Queue, 0, sizeof(APPACTIONQUEUEITEM) * MAXACTIONQUEUE);
  	ResetQueue();
  }
  
  void AppActionQueue::ResetQueue()
  {
  	//SendDebugMessageFormat(0, sdmAIDefault, 0, "App::ResetQueue");
  	QueueSize = 0;   // I4262
  }
  
  BOOL AppActionQueue::QueueAction(int ItemType, DWORD dwData)
  {
  	if(QueueSize > MAXACTIONQUEUE - 1)
  	{
  		MessageBeep(0xFFFFFFFF);
  		return FALSE;
  	}
  
  	Queue[QueueSize].ItemType = ItemType;
  	Queue[QueueSize].dwData   = dwData;
  
  	QueueSize++;
  
  	SendDebugMessageFormat(0, sdmAIDefault, 0, "App::QueueAction: %s %x", ItemTypes[ItemType], dwData);
  
  	return TRUE;
  }
  
  /* AppIntegration */
  
  AppIntegration::AppIntegration()
  {
  	hwnd = NULL;
    FShiftFlags = 0;
  }
  
  BOOL ContextItemsFromAppContext(WCHAR const* buf, km_kbp_context_item** outPtr)
  {
    assert(buf);
    assert(outPtr);
    km_kbp_context_item* context_items  = new km_kbp_context_item[wcslen(buf) + 1];
    WCHAR const *p = buf;
    uint8_t contextIndex = 0;
    while (*p) {
      if (*p == UC_SENTINEL) {
        assert(*(p + 1) == CODE_DEADKEY);
        // we know the only uc_sentinel code in the context is code_deadkey, which has only 1 parameter: uc_sentinel code_deadkey <deadkey_id>
        // setup dead key context item
        p += 2;
        context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_MARKER, {0,}, {*p} };
      } else if (Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p + 1))) {
        // handle surrogate
        context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {(char32_t)Uni_SurrogateToUTF32(*p, *(p + 1))} };
        p++;
      } else {
        context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {*p} };
      }
      p++;
    }
    // terminate the context_items array.
    context_items[contextIndex] = km_kbp_context_item KM_KBP_CONTEXT_ITEM_END;
  
    *outPtr = context_items;
    return true;
  }
  
  
  BOOL
  ContextItemToAppContext(km_kbp_context_item *contextItems, PWSTR outBuf, DWORD len) {
    assert(contextItems);
    assert(outBuf);
  
    km_kbp_context_item *km_kbp_context_it = contextItems;
    uint8_t contextLen               = 0;
    for (; km_kbp_context_it->type != KM_KBP_CT_END; ++km_kbp_context_it) {
      ++contextLen;
    }
  
    WCHAR *buf = new WCHAR[(contextLen*3)+ 1 ]; // *3 if every context item was a deadkey
    uint8_t idx               = 0;
    km_kbp_context_it = contextItems;
    for (; km_kbp_context_it->type != KM_KBP_CT_END; ++km_kbp_context_it) {
      switch (km_kbp_context_it->type) {
      case KM_KBP_CT_CHAR:
        if (Uni_IsSMP(km_kbp_context_it->character)) {
          buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate1(km_kbp_context_it->character);
          buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate2(km_kbp_context_it->character);
        } else {
          buf[idx++] = (km_kbp_cp)km_kbp_context_it->character;
        }
        break;
      case KM_KBP_CT_MARKER:
        assert(km_kbp_context_it->marker > 0);
        buf[idx++] = UC_SENTINEL;
        buf[idx++] = CODE_DEADKEY;
        buf[idx++] = static_cast<WCHAR>(km_kbp_context_it->marker);
        break;
      }
    }
  
    buf[idx] = 0;  // Null terminate character array
  
    if (wcslen(buf) > len) {
      // Truncate to length 'len' using AppContext so that the context closest to the caret is preserved
      // and the truncation will not split deadkeys or surrogate pairs
      // Note by using the app context class we will truncate the context to the MAXCONTEXT length if 'len'
      // is greater than MAXCONTEXT
      AppContext context;
      context.Set(buf);
      context.Get(outBuf, len);
    } else {
      wcscpy_s(outBuf, wcslen(buf) + 1, buf);
    }
    delete[] buf;
    return TRUE;
  }
  