/*
  Name:             inserttext
  Copyright:        Copyright (C) SIL International.
  Documentation:    
  Description:      
  Create Date:      19 Jun 2007

  Modified Date:    28 Mar 2016
  Authors:          mcdurdin
  Related Files:    
  Dependencies:     

  Bugs:             
  Todo:             
  Notes:            
  History:          19 Jun 2007 - mcdurdin - I890 - Fix deadkeys in TSF
                    19 Jun 2007 - mcdurdin - I890 - Fix crash with deadkeys - logging
                    11 Dec 2009 - mcdurdin - Header files
                    22 Mar 2010 - mcdurdin - Compiler fixup
                    24 Oct 2012 - mcdurdin - I3481 - V9.0 - Eliminate unsafe calls in C++
                    17 Nov 2012 - mcdurdin - I3564 - V9.0 - Add 'pseudo' mapping of characters to indicate kmtip is running
                    17 Nov 2012 - mcdurdin - I3565 - V9.0 - Add extra validation and logging to GetLeftOfSelection
                    01 Dec 2012 - mcdurdin - I3607 - V9.0 - Disable 'pseudo' mode in kmtip except for debugging
                    10 Jun 2014 - mcdurdin - I4262 - V9.0 - TSF deadkeys do not function correctly
                    28 Mar 2016 - mcdurdin - I4933 - Compat issue with Firefox 42 and IE and Keyman 9 TSF
*/
#include "pch.h"   // I4262
#include "kmtip.h"

//+---------------------------------------------------------------------------
//
// InsertTextAtSelection
//
//----------------------------------------------------------------------------

#ifdef DEBUG_PSEUDO   // I3607

WCHAR PseudoMap[128] =    // I3564
{
  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
  0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
  0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
  0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
  0x40,L'Ą',L'Ɓ',L'Ĉ',L'Đ',L'Ĕ',L'Ḟ',L'Ĝ',L'Ħ',L'Ĭ',L'Ĵ',L'Ķ',L'Ł',L'Ḿ',L'Ṉ',L'Ṏ',
  L'Ᵽ',L'Ꝙ',L'Ȑ',L'Ṧ',L'Ŧ',L'Ʉ',L'Ṽ',L'Ẃ',L'Ẍ',L'Ɏ',L'Ẕ', 0x5B,0x5C,0x5D,0x5E,0x5F,
  0x60,L'ⱥ',L'ḇ',L'ḉ',L'ḓ',L'ḕ',L'ḟ',L'ḡ',L'ḩ',L'ḭ',L'ɉ',L'ḵ',L'ƚ',L'ᴟ',L'ᴝ',L'ȭ',
  L'ᵽ',L'ʠ',L'ɍ',L'ȿ',L'ŧ',L'ů',L'ⱴ',L'ŵ',L'ᶍ',L'ɏ',L'ƶ', 0x7B,0x7C,0x7D,0x7E,0x7F
  //0x40,'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
  //'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x5B,0x5C,0x5D,0x5E,0x5F,
  //0x60,'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
  //'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0x7B,0x7C,0x7D,0x7E,0x7F
};

void Pseudofy(WCHAR *pchText)   // I3564
{
  for(; *pchText; pchText++)
  {
    if(*pchText < 0x80) *pchText = PseudoMap[*pchText];
  }
}

void DePseudofy(WCHAR *pchText)   // I3564
{
  for(; *pchText; pchText++)
  {
    for(int k = 0; k < 0x80; k++)
    {
      if(*pchText == PseudoMap[k])
      {
        *pchText = k; 
        break;
      }
    }
  }
}
#endif   // I3607

void InsertTextAtSelection(TfEditCookie ec, ITfContext *pContext, const WCHAR *pchText, ULONG cchText)
{
  LogEnter();
  
  ITfInsertAtSelection *pInsertAtSelection;
    ITfRange *pRange;
    TF_SELECTION tfSelection;

    // we need a special interface to insert text at the selection
    if (pContext->QueryInterface(IID_ITfInsertAtSelection, (void **)&pInsertAtSelection) != S_OK)
        return;

#ifdef DEBUG_PSEUDO   // I3607
    WCHAR *PseudoBuf = new WCHAR[cchText+1];   // I3564
    
    wcsncpy_s(PseudoBuf, cchText+1, pchText, cchText);
    PseudoBuf[cchText] = 0;
    Pseudofy(PseudoBuf);

    // insert the text
    if (pInsertAtSelection->InsertTextAtSelection(ec, 0, PseudoBuf, cchText, &pRange) != S_OK)   // I3564
        goto Exit;
#else   // I3607
    // insert the text
    if (pInsertAtSelection->InsertTextAtSelection(ec, 0, pchText, cchText, &pRange) != S_OK)   // I3564
        goto Exit;
#endif

    // update the selection, we'll make it an insertion point just past
    // the inserted text.
    pRange->Collapse(ec, TF_ANCHOR_END);

    tfSelection.range = pRange;
    tfSelection.style.ase = TF_AE_NONE;
    tfSelection.style.fInterimChar = FALSE;

    pContext->SetSelection(ec, 1, &tfSelection);

    pRange->Release();

Exit:

#ifdef DEBUG_PSEUDO   // I3607
    delete PseudoBuf;   // I3564
#endif   // I3607

    pInsertAtSelection->Release();
}

void DeleteLeftOfSelection(TfEditCookie ec, ITfContext *pContext, LONG n)
{
  LogEnter();
  TF_SELECTION tfSelection;
	ULONG cFetched;
	LONG outn;

  if(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK || cFetched == 0)
		return;
	
  //TODO: log failures
	if(tfSelection.range->ShiftStart(ec, -n, &outn, NULL) != S_OK) {
    tfSelection.range->Release();
    return;
  }

  WCHAR buf[2] = L"";
	if(tfSelection.range->SetText(ec, 0, buf, 0) != S_OK) {
    tfSelection.range->Release();
    return;
  }

  // release the range
  tfSelection.range->Release();
}

char *debugstr(PWSTR buf) {
	WCHAR *p;
	char *bufout = new char[20*7+1];
	char *q;
  *bufout = 0;
	for(p = buf, q = bufout; *p && (p-buf < 20); p++)
	{
		wsprintf(q, "U+%4.4X ", *p); q = strchr(q, 0);
	}
	//WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL);
	return bufout;
}

BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG n)   // I4933
{
  LogEnter();

  TF_SELECTION tfSelection = {0};
  TF_STATUS tfStatus = {0};
	ULONG cFetched;
	LONG outn;
	ITfRange *pRange, *pRangeEnd;
  HRESULT hr;

  /*   // I4933
    First we will try to see if there is any text in the control, and if not, then treat
    it as transitory (that is, no ability to read context. This seems to happen in Firefox 
    with RICHEDIT controls - for example, SourceForge comment fields e.g. reported on page
    https://sourceforge.net/p/greekpolytonicsp/discussion/general/thread/9b6fa46d/
    This also happens in Internet Explorer in the same fields. It is unclear at this point
    if there is the same root cause or if Firefox just happens to be copying IE behaviour.
  */

  if(!SUCCEEDED(hr = pContext->GetStart(ec, &pRange))) {
    Log(L"GetLeftOfSelection: Exit -- Failed GetStart = %x", hr);   // I3565
		return FALSE;
	}

  if(!SUCCEEDED(hr = pContext->GetEnd(ec, &pRangeEnd))) {
    Log(L"GetLeftOfSelection: Exit -- Failed GetEnd = %x", hr);   // I3565
    pRange->Release(); pRange = NULL;
		return FALSE;
	}

  if(!SUCCEEDED(hr = pRange->ShiftEndToRange(ec, pRangeEnd, TF_ANCHOR_END))) {
    Log(L"GetLeftOfSelection: Exit -- Failed ShiftEndToRange = %x", hr);
    pRange->Release(); pRange = NULL;
    pRangeEnd->Release(); pRangeEnd = NULL;
    return FALSE;
  }

  BOOL bTreatAsTransitory = FALSE; 
  if(!SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetched))) {
    Log(L"GetLeftOfSelection: Exit -- Failed GetRange (all text to 63 chars) = %x", hr);
    bTreatAsTransitory = TRUE;
  } else if(cFetched == 0) {
    Log(L"GetLeftOfSelection: Exit -- no text in edit control, treating as transitory");
    bTreatAsTransitory = TRUE;
  }

  pRange->Release(); pRange = NULL;
  pRangeEnd->Release(); pRangeEnd = NULL;

  if(bTreatAsTransitory) {
    buf[0] = 0;
    return FALSE;
  }

  /* 
    At this point, we know we can read content from the edit control, so we just need
    to read the range to the left of the selection - up to (n) characters.
  */

  if(!SUCCEEDED(hr = pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched)))   // I3565
	{
		Log(L"GetLeftOfSelection: Exit -- Failed GetSelection = %x", hr);   // I3565
		return FALSE;
	}

  if(cFetched == 0)   // I3565
  {
    Log(L"GetLeftOfSelection: Exit -- cFetched == 0");   // I3565
    if(tfSelection.range != NULL)
      tfSelection.range->Release();
    return FALSE;
  }
	
	if(!SUCCEEDED(hr = tfSelection.range->Clone(&pRange)))   // I3565
  {
    Log(L"GetLeftOfSelection: Failed range->Clone = %x", hr);   // I3565
    tfSelection.range->Release();
    return FALSE;
  }

	if(!SUCCEEDED(hr = pRange->Collapse(ec, TF_ANCHOR_START)))   // I3565
  {
    Log(L"GetLeftOfSelection: Failed range->Collapse = %x", hr);   // I3565
    tfSelection.range->Release();
    pRange->Release();
    return FALSE;
  }
	if(!SUCCEEDED(hr = pRange->ShiftStart(ec, -n, &outn, NULL)))   // I3565
  {
    Log(L"GetLeftOfSelection: Failed range->ShiftStart = %x", hr);   // I3565
    tfSelection.range->Release();
    pRange->Release();
    return FALSE;
  }

  BOOL result = TRUE;

	if(SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetched)))   // I3565
  {
		buf[cFetched] = 0;
    if(ShouldDebug()) {
      char *p = debugstr(buf);
      Log(L"GetLeftOfSelection(%d) = %hs [%d fetched]", n, p, cFetched);
      delete[] p;
    }
#ifdef DEBUG_PSEUDO   // I3607
    DePseudofy(buf);   // I3564
#endif   // I3607
  }
	else
  {
    Log(L"GetLeftOfSelection: Failed range->GetText = %x", hr);   // I3565
    buf[0] = 0;
    result = FALSE;
  }

	pRange->Release();
  tfSelection.range->Release();

  return result;   // I4933
}
