#include <farplug-wide.h>
#include "AlignLng.hpp"
#include "Align.hpp"
#include "AlignMix.icpp"

#include <utils.h>
#include <KeyFileHelper.h>

#define INI_LOCATION	InMyConfig("plugins/align/config.ini")
#define INI_SECTION		"Settings"

#ifndef UNICODE
#define GetCheck(i) DialogItems[i].Selected
#define GetDataPtr(i) DialogItems[i].Data
#else
#define GetCheck(i) (int)Info.SendDlgMessage(hDlg,DM_GETCHECK,i,0)
#define GetDataPtr(i) ((const TCHAR *)Info.SendDlgMessage(hDlg,DM_GETCONSTTEXTPTR,i,0))
#endif


static void ReformatBlock(int RightMargin,int SmartMode,int Justify);
static void JustifyBlock(int RightMargin);
static int JustifyString(int RightMargin,struct EditorSetString &ess);

SHAREDSYMBOL int WINAPI EXP_NAME(GetMinFarVersion)()
{
  return FARMANAGERVERSION;
}


SHAREDSYMBOL void WINAPI EXP_NAME(SetStartupInfo)(const struct PluginStartupInfo *Info)
{
  ::Info=*Info;
  ::FSF=*Info->FSF;
  ::Info.FSF=&::FSF;
}

SHAREDSYMBOL HANDLE WINAPI EXP_NAME(OpenPlugin)(int OpenFrom,INT_PTR Item)
{
  struct InitDialogItem InitItems[]={
    {DI_DOUBLEBOX,3,1,72,8,0,0,0,0,(TCHAR *)MAlign},
    {DI_FIXEDIT,5,2,7,3,1,0,0,0,_T("")},
    {DI_TEXT,9,2,0,0,0,0,0,0,(TCHAR *)MRightMargin},
    {DI_CHECKBOX,5,3,0,0,0,0,0,0,(TCHAR *)MReformat},
    {DI_CHECKBOX,5,4,0,0,0,0,0,0,(TCHAR *)MSmartMode},
    {DI_CHECKBOX,5,5,0,0,0,0,0,0,(TCHAR *)MJustify},
    {DI_TEXT,5,6,0,0,0,0,DIF_BOXCOLOR|DIF_SEPARATOR,0,_T("")},
    {DI_BUTTON,0,7,0,0,0,0,DIF_CENTERGROUP,1,(TCHAR *)MOk},
    {DI_BUTTON,0,7,0,0,0,0,DIF_CENTERGROUP,0,(TCHAR *)MCancel}
  };

  struct FarDialogItem DialogItems[ARRAYSIZE(InitItems)];
  InitDialogItems(InitItems,DialogItems,ARRAYSIZE(InitItems));
  KeyFileHelper kfh(INI_LOCATION);
  int RightMargin=kfh.GetInt(INI_SECTION,("RightMargin"),75);
  int Reformat=kfh.GetInt(INI_SECTION,("Reformat"),TRUE);
  int SmartMode=kfh.GetInt(INI_SECTION,("SmartMode"),FALSE);
  int Justify=kfh.GetInt(INI_SECTION,("Justify"),FALSE);
#ifdef UNICODE
  wchar_t marstr[32];
  DialogItems[1].PtrData = marstr;
  FSF.snprintf(marstr,ARRAYSIZE(marstr),L"%d",RightMargin);
#else
  FSF.snprintf(DialogItems[1].Data,ARRAYSIZE(DialogItems[1].Data),"%d",RightMargin);
#endif
  DialogItems[3].Selected=Reformat;
  DialogItems[4].Selected=SmartMode;
  DialogItems[5].Selected=Justify;
#ifndef UNICODE
  int ExitCode=Info.Dialog(Info.ModuleNumber,-1,-1,76,10,NULL,DialogItems,
                           ARRAYSIZE(DialogItems));
#else
  HANDLE hDlg=Info.DialogInit(Info.ModuleNumber,-1,-1,76,10,NULL,DialogItems,
                              ARRAYSIZE(DialogItems),0,0,NULL,0);
  if (hDlg == INVALID_HANDLE_VALUE)
    return INVALID_HANDLE_VALUE;

  int ExitCode=Info.DialogRun(hDlg);
#endif
  if (ExitCode!=7)
    goto done;
  RightMargin=FSF.atoi(GetDataPtr(1));
  Reformat=GetCheck(3);
  SmartMode=GetCheck(4);
  Justify=GetCheck(5);
  kfh.SetInt(INI_SECTION,("Reformat"),Reformat);
  kfh.SetInt(INI_SECTION,("RightMargin"),RightMargin);
  kfh.SetInt(INI_SECTION,("SmartMode"),SmartMode);
  kfh.SetInt(INI_SECTION,("Justify"),Justify);
  Info.EditorControl(ECTL_TURNOFFMARKINGBLOCK,NULL);
  if (Reformat)
    ReformatBlock(RightMargin,SmartMode,Justify);
  else
    if (Justify)
      JustifyBlock(RightMargin);
done:
#ifdef UNICODE
  Info.DialogFree(hDlg);
#endif
  return(INVALID_HANDLE_VALUE);
}


void ReformatBlock(int RightMargin,int SmartMode,int Justify)
{
  EditorInfo ei;
  Info.EditorControl(ECTL_GETINFO,&ei);

  if (ei.BlockType!=BTYPE_STREAM || RightMargin<1)
    return;

  struct EditorSetPosition esp;
  memset(&esp,-1,sizeof(esp));
  esp.CurLine=ei.BlockStartLine;
  esp.CurPos=0;
  Info.EditorControl(ECTL_SETPOSITION,&esp);

  TCHAR *TotalString=NULL;
  int TotalLength=0,IndentSize=0x7fffffff;

  while (1)
  {
    struct EditorGetString egs;
    egs.StringNumber=-1;
    if (!Info.EditorControl(ECTL_GETSTRING,&egs))
      break;
    if (egs.SelStart==-1 || egs.SelStart==egs.SelEnd)
      break;
    int ExpandNum=-1;
    Info.EditorControl(ECTL_EXPANDTABS,&ExpandNum);
    Info.EditorControl(ECTL_GETSTRING,&egs);

    int SpaceLength=0;
    while (SpaceLength<egs.StringLength && egs.StringText[SpaceLength]==_T(' '))
      SpaceLength++;

    while (egs.StringLength>0 && *egs.StringText==_T(' '))
    {
      egs.StringText++;
      egs.StringLength--;
    }

    if (egs.StringLength>0)
    {
      if (SpaceLength<IndentSize)
        IndentSize=SpaceLength;

      TotalString=(TCHAR *)realloc(TotalString,(TotalLength+egs.StringLength+2)*sizeof(TCHAR));
      if (TotalLength!=0 && TotalString[TotalLength-1]!=_T(' '))
        TotalString[TotalLength++]=_T(' ');

      _tmemcpy(TotalString+TotalLength,egs.StringText,egs.StringLength);
      TotalLength+=egs.StringLength;
    }
    if (!Info.EditorControl(ECTL_DELETESTRING,NULL))
    {
      free(TotalString);
      return;
    }
  }
  if(TotalString)
    TotalString[TotalLength++]=_T(' ');

  if (IndentSize>=RightMargin)
    IndentSize=RightMargin-1;

  const int MaxIndent=1024;
  if (IndentSize>=MaxIndent)
    IndentSize=MaxIndent-1;

  TCHAR IndentBuf[MaxIndent];
  if (IndentSize>0)
  {
    _tmemset(IndentBuf,_T(' '),IndentSize);
    IndentBuf[IndentSize]=0;
  }

  Info.EditorControl(ECTL_SETPOSITION,&esp);
  Info.EditorControl(ECTL_INSERTSTRING,NULL);
  Info.EditorControl(ECTL_SETPOSITION,&esp);

  int LastSplitPos=0,PrevSpacePos;
  while (LastSplitPos<TotalLength && TotalString[LastSplitPos]==_T(' '))
    LastSplitPos++;
  PrevSpacePos=LastSplitPos;

  for (int I=LastSplitPos;I<TotalLength;I++)
  {
    int Length=I-LastSplitPos;
    int LastLength=PrevSpacePos-LastSplitPos;
    if (TotalString[I]==_T(' ') && Length>RightMargin-IndentSize && LastLength<=RightMargin-IndentSize)
    {
      if (LastLength<=0)
      {
        PrevSpacePos=I;
        LastLength=Length;
      }

      if (SmartMode)
      {
        int Space1=-1,Space2=-1;
        for (int J=PrevSpacePos-1;J>LastSplitPos+20;J--)
        {
          if (TotalString[J]==_T(' '))
          {
            if (Space2==-1)
              Space2=J;
            else
            {
              if (Space1==-1)
                Space1=J;
              else
                break;
            }
          }
        }
        if (Space2!=-1 && PrevSpacePos-Space2<4)
          if (Space1==-1 || Space2-Space1>4 || PrevSpacePos-Space2==2)
          {
            PrevSpacePos=Space2;
            while (PrevSpacePos>LastSplitPos+1 && TotalString[PrevSpacePos-1]==_T(' '))
              PrevSpacePos--;
            LastLength=PrevSpacePos-LastSplitPos;
          }
      }

      I=PrevSpacePos;

      struct EditorSetString ess;
      ess.StringNumber=-1;
      ess.StringText=TotalString+LastSplitPos;
      ess.StringEOL=NULL;
      ess.StringLength=LastLength;
      while (ess.StringLength>0 && ess.StringText[ess.StringLength-1]==_T(' '))
        ess.StringLength--;
      if (!Justify || ess.StringLength>=RightMargin-IndentSize || !JustifyString(RightMargin-IndentSize,ess))
        Info.EditorControl(ECTL_SETSTRING,&ess);
      else
        LastLength=RightMargin;

      while (I<TotalLength && TotalString[I]==_T(' '))
        PrevSpacePos=I++;

      struct EditorSetPosition esp;
      memset(&esp,-1,sizeof(esp));
      esp.CurLine=-1;

      if (IndentSize>0)
      {
        esp.CurPos=0;
        Info.EditorControl(ECTL_SETPOSITION,&esp);
        Info.EditorControl(ECTL_INSERTTEXT,(void *)IndentBuf);
      }

      esp.CurPos=LastLength+IndentSize;
      Info.EditorControl(ECTL_SETPOSITION,&esp);
      Info.EditorControl(ECTL_INSERTSTRING,NULL);

      LastSplitPos=I;
    }
    if (TotalString[I]==_T(' '))
      PrevSpacePos=I;
  }
  struct EditorSetString ess;
  ess.StringNumber=-1;
  ess.StringText=TotalString+LastSplitPos;
  ess.StringEOL=NULL;
  ess.StringLength=TotalLength-LastSplitPos;
  while (ess.StringLength>0 && ess.StringText[ess.StringLength-1]==_T(' '))
    ess.StringLength--;
  Info.EditorControl(ECTL_SETSTRING,&ess);

  if (IndentSize>0)
  {
    struct EditorSetPosition esp;
    memset(&esp,-1,sizeof(esp));
    esp.CurLine=-1;
    esp.CurPos=0;
    Info.EditorControl(ECTL_SETPOSITION,&esp);
    Info.EditorControl(ECTL_INSERTTEXT,(void *)IndentBuf);
  }

  free(TotalString);

  memset(&esp,-1,sizeof(esp));
  esp.CurLine=ei.CurLine;
  esp.CurPos=ei.CurPos;
  Info.EditorControl(ECTL_SETPOSITION,&esp);
}


void JustifyBlock(int RightMargin)
{
  EditorInfo ei;
  Info.EditorControl(ECTL_GETINFO,&ei);

  if (ei.BlockType!=BTYPE_STREAM)
    return;

  struct EditorGetString egs;
  egs.StringNumber=ei.BlockStartLine;

  while (1)
  {
    if (!Info.EditorControl(ECTL_GETSTRING,&egs))
      break;
    if (egs.SelStart==-1 || egs.SelStart==egs.SelEnd)
      break;
    int ExpNum=egs.StringNumber;
    if (!Info.EditorControl(ECTL_EXPANDTABS,&ExpNum))
      break;
    Info.EditorControl(ECTL_GETSTRING,&egs);

    struct EditorSetString ess;
    ess.StringNumber=egs.StringNumber;

    ess.StringText=(TCHAR*)egs.StringText;
    ess.StringEOL=(TCHAR*)egs.StringEOL;
    ess.StringLength=egs.StringLength;

    if (ess.StringLength<RightMargin)
      JustifyString(RightMargin,ess);

    egs.StringNumber++;
  }
}


int JustifyString(int RightMargin,struct EditorSetString &ess)
{
  int WordCount=0;
  int I;
  for (I=0;I<ess.StringLength-1;I++)
    if (ess.StringText[I]!=_T(' ') && ess.StringText[I+1]==_T(' '))
      WordCount++;
  if (ess.StringLength>0 && ess.StringText[ess.StringLength-1]==_T(' '))
    WordCount--;
  if (WordCount<=0)
    return(FALSE);
  while (ess.StringLength>0 && ess.StringText[ess.StringLength-1]==_T(' '))
    ess.StringLength--;
  int TotalAddSize=RightMargin-ess.StringLength;
  int AddSize=TotalAddSize/WordCount;
  int Reminder=TotalAddSize%WordCount;

  TCHAR *NewString=(TCHAR *)malloc(RightMargin*sizeof(TCHAR));
  _tmemset(NewString,_T(' '),RightMargin);
  _tmemcpy(NewString,ess.StringText,ess.StringLength);

  for (I=0;I<RightMargin-1;I++)
    if (NewString[I]!=_T(' ') && NewString[I+1]==_T(' '))
    {
      int MoveSize=AddSize;
      if (Reminder)
      {
        MoveSize++;
        Reminder--;
      }
      if (MoveSize==0)
        break;
      memmove(NewString+I+1+MoveSize,NewString+I+1,(RightMargin-(I+1+MoveSize))*sizeof(TCHAR));
      while (MoveSize--)
        NewString[I+1+MoveSize]=_T(' ');
    }

  ess.StringText=NewString;
  ess.StringLength=RightMargin;
  Info.EditorControl(ECTL_SETSTRING,&ess);
  free(NewString);
  return(TRUE);
}


SHAREDSYMBOL void WINAPI EXP_NAME(GetPluginInfo)(struct PluginInfo *Info)
{
  Info->StructSize=sizeof(*Info);
  Info->Flags=PF_EDITOR|PF_DISABLEPANELS;
  Info->DiskMenuStringsNumber=0;
  static const TCHAR *PluginMenuStrings[1];
  PluginMenuStrings[0]=GetMsg(MAlign);
  Info->PluginMenuStrings=PluginMenuStrings;
  Info->PluginMenuStringsNumber=ARRAYSIZE(PluginMenuStrings);
  Info->PluginConfigStringsNumber=0;
}
