
#include <nds.h>

#include "_const.h"
#include "maindef.h"
#include "_console.h"
#include "_consoleWriteLog.h"

#include "memtool.h"
#undef safemalloc
#define safemalloc safemalloc_chkmem
#include "strtool.h"
#include "unicode.h"
#include "cwl.h"
#include "fat2.h"
#include "datetime.h"
#include "shell.h"
#include "extlink.h"
#include "inifile.h"
#include "procstate.h"
#include "strpcm.h"
#include "sndeff.h"
#include "PlaySE.h"

#include "OnSkin.h"

#include "TextEdit.h"

#define pTestFont pCglFontDefault

#define DispLinesCount (9)
#define DispLinesMarginCount (4)

bool TextEdit_ChangedFlag;

// ---------------------------------------------------------------

class CTextEdit
{
protected:
public:
  CTextEdit(void);
  ~CTextEdit(void);
  CTextEditLine *pTopLine;
};

static CTextEdit *pTextEdit;

CTextEdit::CTextEdit(void)
{
  pTopLine=new CTextEditLine();
  pTopLine->pBack=NULL;
  pTopLine->pNext=NULL;
}

CTextEdit::~CTextEdit(void)
{
  while(pTopLine->pNext!=NULL){
    delete pTopLine->pNext;
  }
  
  if(pTopLine!=NULL){
    delete pTopLine; pTopLine=NULL;
  }
}

// ---------------------------------------------------------------

static s32 CursorPos;
static u32 ShowTopLineIndex;
static u32 AjustPixelsForHoriMove;

static u32 SelectTopPos;

void TextEdit_Init(void)
{
  TextEdit_ChangedFlag=false;
  
  pTextEdit=new CTextEdit();
  
  CursorPos=0;
  ShowTopLineIndex=0;
  AjustPixelsForHoriMove=(u32)-1;
  
  SelectTopPos=(u32)-1;
}

void TextEdit_Free(void)
{
  if(pTextEdit!=NULL){
    delete pTextEdit; pTextEdit=NULL;
  }
}

void TextEdit_CheckLinkStructure(void)
{
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  u32 idx=0;
  
  while(1){
    if(pCurLine->pNext==NULL) break;
    idx++;
    pCurLine=pCurLine->pNext;
  }
  
  _consolePrintf("Total lines count= %d.\n",idx);
  
  while(1){
    if(pCurLine->pBack==NULL) break;
    idx--;
    pCurLine=pCurLine->pBack;
  }
  
  if(pTextEdit->pTopLine!=pCurLine) StopFatalError(0,"Illigal Link structure.");
  if(idx!=0) StopFatalError(0,"Illigal Back link pointer.");
  
  _consolePrintf("Pointer link structure checked.\n");
}

static void TextEdit_ClearAllText(void)
{
  if(pTextEdit!=NULL){
    delete pTextEdit; pTextEdit=NULL;
  }
  
  pTextEdit=new CTextEdit();
}

typedef struct {
  u32 x,y;
  CTextEditLine *pCurLine;
  const TLineState *pLineState;
  u32 LineStateIndex;
  u32 LeftPixels;
} TCurrentInfo;

static TCurrentInfo TextEdit_GetCurrentInfoFromPosition(u32 pos)
{
  TCurrentInfo res;
  res.x=0;
  res.y=0;
  res.pCurLine=NULL;
  res.pLineState=NULL;
  res.LineStateIndex=0;
  res.LeftPixels=0;
  
  res.pCurLine=pTextEdit->pTopLine;
  
  while(1){
    u32 len=res.pCurLine->GetLength();
    res.pLineState=res.pCurLine->GetLineState();
    if(pos<len) break;
    pos-=len;
    res.y+=res.pLineState->LinesCount;
    res.pCurLine=res.pCurLine->pNext;
    if(res.pCurLine==NULL) StopFatalError(0,"Illigal cursor position. (%d)",pos);
  }
  
  res.LineStateIndex=0;
  while(1){
    const TLineState_TLine *pcurline=&res.pLineState->pLines[res.LineStateIndex];
    if(pos<pcurline->Length) break;
    pos-=pcurline->Length;
    res.y++;
    res.LineStateIndex++;
    if(res.LineStateIndex==res.pLineState->LinesCount) StopFatalError(0,"Line seek overflow.");
  }
  
  res.x=pos;
  
  const TLineState_TLine *pcurline=&res.pLineState->pLines[res.LineStateIndex];
  const UnicodeChar *psrcstr=pcurline->pstr;
  res.LeftPixels=0;
  while(1){
    UnicodeChar wc=*psrcstr++;
    if((wc==0)||(wc==WC_Enter)) break;
    if(pos==0) break;
    pos--;
    res.LeftPixels+=pTestFont->GetFontWidth(wc)+FontWidthMargin;
  }
  
  return(res);
}

static bool TextEdit_AddCursor(s32 Vector)
{
  AjustPixelsForHoriMove=(u32)-1;
  
  if(Vector<=0){
    CursorPos+=Vector;
    if(CursorPos<0){
      CursorPos=0;
      ShowTopLineIndex=0;
      return(false);
    }
    TCurrentInfo ci=TextEdit_GetCurrentInfoFromPosition(CursorPos);
    if(ci.y<ShowTopLineIndex) ShowTopLineIndex=ci.y;
    return(true);
  }
  
  CursorPos+=Vector;
  u32 pos=CursorPos;
  
  u32 TotalLength=0;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  bool f;
  
  while(1){
    u32 len=pCurLine->GetLength();
    if(pos<len){
      f=true;
      break;
    }
    pos-=len;
    TotalLength+=len;
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL){
      CursorPos=TotalLength-1;
      f=false;
      break;
    }
  }
  
  TCurrentInfo ci=TextEdit_GetCurrentInfoFromPosition(CursorPos);
  if((ShowTopLineIndex+(DispLinesCount-1))<ci.y) ShowTopLineIndex=ci.y-(DispLinesCount-1);
  return(f);
}

static bool TextEdit_HoriMoveCursor(s32 Vector)
{
  u32 y;
  
  {
    TCurrentInfo ci=TextEdit_GetCurrentInfoFromPosition(CursorPos);
    if(AjustPixelsForHoriMove==(u32)-1) AjustPixelsForHoriMove=ci.LeftPixels;
    y=ci.y;
    u32 lasty=y;
    while(Vector<0){
      if(y==0) break;
      y--;
      Vector++;
    }
    while(0<Vector){
      ci.LineStateIndex++;
      if(ci.pLineState->LinesCount<=ci.LineStateIndex){
        if(ci.pCurLine->pNext==NULL) break;
        ci.pCurLine=ci.pCurLine->pNext;
        ci.pLineState=ci.pCurLine->GetLineState();
        ci.LineStateIndex=0;
      }
      y++;
      Vector--;
    }
    if(lasty==y) return(false);
  }
  
  if(y<ShowTopLineIndex) ShowTopLineIndex=y;
  if((ShowTopLineIndex+(DispLinesCount-1))<y) ShowTopLineIndex=y-(DispLinesCount-1);
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  CursorPos=0;
  
  while(1){
    const TLineState *pLineState=pCurLine->GetLineState();
    if(y<pLineState->LinesCount) break;
    y-=pLineState->LinesCount;
    CursorPos+=pCurLine->GetLength();
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) StopFatalError(0,"Illigal line position. (%d)",y);
  }
  
  const TLineState *pLineState=pCurLine->GetLineState();
  
  for(u32 idx=0;idx<y;idx++){
    CursorPos+=pLineState->pLines[idx].Length;
  }
  
  const UnicodeChar *pstr=pLineState->pLines[y].pstr;
  u32 pix=0;
  while(1){
    UnicodeChar wc=*pstr++;
    if((wc==0)||(wc==WC_Enter)) break;
//    if(AjustPixelsForHoriMove<=pix) break; // Adjust to right.
    u32 w=pTestFont->GetFontWidth(wc)+FontWidthMargin;
    if(AjustPixelsForHoriMove<(pix+(w/2))) break; // Adjust to near.
//    if(AjustPixelsForHoriMove<(pix+w)) break; // Adjust to left.
    pix+=w;
    CursorPos++;
  }
  
  return(true);
}

void TextEdit_Add(const UnicodeChar *pstr)
{
  s32 pos=CursorPos;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  while(1){
    u32 len=pCurLine->GetLength();
    if(pos<len) break;
    pos-=len;
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) StopFatalError(0,"Illigal cursor position. (%d)",CursorPos);
  }
  
  TextEdit_ChangedFlag=true;
  pCurLine->AddStr(pos,pstr);
  
  TextEdit_AddCursor(Unicode_GetLength(pstr));
}

bool TextEdit_MoveCursor(u32 keys)
{
  if((keys&KEY_UP)!=0){
    return(TextEdit_HoriMoveCursor(-1));
  }
  
  if((keys&KEY_DOWN)!=0){
    return(TextEdit_HoriMoveCursor(1));
  }
  
  if((keys&KEY_X)!=0){
    return(TextEdit_HoriMoveCursor(-(DispLinesCount/2)));
  }
  
  if((keys&KEY_Y)!=0){
    return(TextEdit_HoriMoveCursor(DispLinesCount/2));
  }
  
  if((keys&KEY_LEFT)!=0){
    return(TextEdit_AddCursor(-1));
  }
  
  if((keys&KEY_RIGHT)!=0){
    return(TextEdit_AddCursor(1));
  }
  
  return(false);
}

bool TextEdit_AddEnter(void)
{
  s32 pos=CursorPos;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  while(1){
    u32 len=pCurLine->GetLength();
    if(pos<len) break;
    pos-=len;
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) StopFatalError(0,"Illigal cursor position. (%d)",CursorPos);
  }
  
  TextEdit_ChangedFlag=true;
  pCurLine->Split(pos);
  
  TextEdit_AddCursor(1);
  
  return(true);
}

bool TextEdit_Space(void)
{
  const UnicodeChar str[2]={WC_Space,0};
  TextEdit_ChangedFlag=true;
  TextEdit_Add(str);
  return(true);
}

bool TextEdit_Del(void)
{
  s32 pos=CursorPos;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  while(1){
    u32 len=pCurLine->GetLength();
    if(pos<len) break;
    pos-=len;
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) StopFatalError(0,"Illigal cursor position. (%d)",CursorPos);
  }
  
  TextEdit_ChangedFlag=true;
  
  if(pos==0){
    if(pCurLine->pBack==NULL) return(false);
    UnicodeChar *pstr=(UnicodeChar*)pCurLine->GetPlaneStringBuffer(); // どうせ捨てるので加工しちゃう。
    pstr[Unicode_GetLength(pstr)-1]=0; // WC_Enterを除去
    pCurLine->pBack->AddStr((u32)-1,pstr);
    delete pCurLine; pCurLine=NULL;
    }else{
    pCurLine->DeleteOne(pos);
  }
  
  TextEdit_AddCursor(-1);
  return(true);
}

static void DrawScrollBar(CglCanvas *pcan)
{
  s32 pos=CursorPos;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  u32 LinesIndex=0;
  u32 LinesCount=0;
  
  while(1){
    u32 len=pCurLine->GetLength();
    if((0<=pos)&&(pos<len)){
      LinesIndex=LinesCount;
      }else{
    }
    pos-=len;
    LinesCount++;
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) break;
  }
  
  const u32 w=64,h=16;
  const u32 x=ScreenWidth-w,y=ScreenHeight-h;
  
  OnSkin_FillBox75per(pcan,x,y,w,h,RGB15(31,31,31)|BIT15);
  pcan->SetColor(RGB15(24,24,24)|BIT15);
  pcan->DrawBox(x,y,w,h);
  
  char str[32];
  snprintf(str,64,"%d / %d",1+LinesIndex,LinesCount);
  const u32 tw=pcan->GetTextWidthA(str),th=glCanvasTextHeight;
  
  pcan->SetFontTextColor(RGB15(8,8,8)|BIT15);
  pcan->TextOutA(x+((w-tw)/2),y+((h-th)/2),str);
  
  if(LinesCount<16) return;
  
  u32 bary=(LinesIndex*ScreenHeight)/(LinesCount+16);
  u32 barh=(16*ScreenHeight)/LinesCount;
  if(ScreenHeight<barh) barh=ScreenHeight;
  if(barh<8) barh=8;
  if(ScreenHeight<(bary+barh)) bary=ScreenHeight-barh;
  
  OnSkin_FillBox25per(pcan,ScreenWidth-8-2,bary,8,barh,RGB15(0,8,0)|BIT15);
}

void TextEdit_Draw(CglCanvas *pcan,bool DrawCursorFlag)
{
  u32 SelTopPos;
  u32 SelEndPos;
  
  if(SelectTopPos==(u32)-1){
    SelTopPos=(u32)-1;
    SelEndPos=(u32)-1;
    }else{
    SelTopPos=SelectTopPos;
    SelEndPos=CursorPos;
    if(SelEndPos<SelTopPos){
      u32 tmp=SelEndPos;
      SelEndPos=SelTopPos;
      SelTopPos=tmp;
    }
  }
  
  const u32 h=ScreenHeight;
  u32 ty=2;
  const u32 th=glCanvasTextHeight+2;
  
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  const TLineState *pCurLineState=pCurLine->GetLineState();
  u32 CurLineStateIndex=0;
  
  u32 TextPosition=0;
  
  s32 curpos=CursorPos;
  
  u32 SkipLinesCount=ShowTopLineIndex;
  
  while(SkipLinesCount!=0){
    SkipLinesCount--;
    const TLineState_TLine *pcurline=&pCurLineState->pLines[CurLineStateIndex];
    const u32 curstrlen=pcurline->Length;
    curpos-=curstrlen;
    TextPosition+=curstrlen;
    CurLineStateIndex++;
    if(CurLineStateIndex==pCurLineState->LinesCount){
      pCurLine=pCurLine->pNext;
      if(pCurLine==NULL) break;
      pCurLineState=pCurLine->GetLineState();
      CurLineStateIndex=0;
    }
  }
  
  if(pCurLine==NULL){
    _consolePrintf("Display seek overflow?\n");
    return;
  }
  
  for(u32 idx=0;idx<DispLinesCount+DispLinesMarginCount;idx++){
    u16 TextColor;
    if(idx<DispLinesCount){
      TextColor=RGB15(0,0,0)|BIT15;
      }else{
      TextColor=RGB15(8,8,8)|BIT15;
    }
    const TLineState_TLine *pcurline=&pCurLineState->pLines[CurLineStateIndex];
    const UnicodeChar *pcurstr=pcurline->pstr;
    const u32 curstrlen=pcurline->Length;
    if((0<=curpos)&&(curpos<pcurline->Length)){
      pcan->SetColor(RGB15(28,28,28)|BIT15);
      pcan->DrawLine(0,ty-1,ScreenWidth,ty-1);
      OnSkin_FillBox50per(pcan,0,ty,ScreenWidth,th-1,RGB15(26,26,31)|BIT15);
      pcan->SetColor(RGB15(24,24,24)|BIT15);
      pcan->DrawLine(0,ty+th-1,ScreenWidth,ty+th-1);
      }else{
      pcan->SetColor(RGB15(28,28,28)|BIT15);
      for(u32 x=1;x<ScreenWidth;x+=3){
        pcan->SetPixel(x,ty+th-1,RGB15(26,26,26)|BIT15);
      }
    }
    {
      u32 tx=0;
      u32 idx=0;
      while(1){
        UnicodeChar wc=*pcurstr++;
        if(wc==0) break;
        if((SelTopPos<=(TextPosition+idx))&&((TextPosition+idx)<SelEndPos)){
          const u32 tw=pTestFont->GetFontWidth(wc)+FontWidthMargin;
          OnSkin_FillBox75per(pcan,tx+1,ty-1,tw,th+1,RGB15(0,0,8)|BIT15);
          pcan->SetFontTextColor(RGB15(28,28,31)|BIT15);
          }else{
          pcan->SetFontTextColor(TextColor);
        }
        if((DrawCursorFlag==true)&&(curpos==idx)){
          const u32 x=2+tx-1,y=ty,h=th;
          pcan->SetColor(RGB15(0,0,0)|BIT15);
          pcan->DrawLine(x+0,y,x+0,y+h);
          pcan->SetColor(RGB15(26,26,28)|BIT15);
          pcan->DrawLine(x+1,y,x+1,y+h);
        }
        if(wc!=WC_Enter){
          UnicodeChar tmp[2]={wc,0};
          const u32 tw=pTestFont->GetFontWidth(wc)+FontWidthMargin;
          if(WindowRectWidth<(tx+tw)) break;
          pcan->TextOutW(2+tx,ty+1,tmp);
          tx+=tw;
          }else{
          const u16 col=RGB15(8,24,8)|BIT15;
          u32 x=2+tx,y=ty+1;
          x+=3; y+=1;
          for(u32 idx=0;idx<8;idx++){
            pcan->SetPixel(x,y,col);
            y++;
          }
          pcan->SetPixel(x-2,y-2,col);
          pcan->SetPixel(x-1,y-1,col);
          pcan->SetPixel(x+2,y-2,col);
          pcan->SetPixel(x+1,y-1,col);
          pcan->SetPixel(x,y,col);
        }
        idx++;
      }
    }
    curpos-=curstrlen;
    TextPosition+=curstrlen;
    ty+=th;
    if(h<(ty+th)) break;
    CurLineStateIndex++;
    if(CurLineStateIndex==pCurLineState->LinesCount){
      pCurLine=pCurLine->pNext;
      if(pCurLine==NULL) break;
      pCurLineState=pCurLine->GetLineState();
      CurLineStateIndex=0;
    }
  }
  
  {
    u32 y=2+(th*DispLinesCount)-1;
    for(u32 x=1;x<ScreenWidth;x+=3){
      pcan->SetPixel(x,y,RGB15(8,8,8)|BIT15);
    }
  }
  
  DrawScrollBar(pcan);
}

void TextEdit_DrawCursorLine(CglCanvas *pcan,const u32 top,const bool DrawCursorFlag)
{
  const UnicodeChar *pcurstr;
  s32 curpos=CursorPos;
  
  {
    CTextEditLine *pCurLine=pTextEdit->pTopLine;
    const TLineState *pCurLineState=pCurLine->GetLineState();
    u32 CurLineStateIndex=0;
    
    while(1){
      const TLineState_TLine *pcurline=&pCurLineState->pLines[CurLineStateIndex];
      const u32 curstrlen=pcurline->Length;
      if(curpos<curstrlen) break;
      curpos-=curstrlen;
      CurLineStateIndex++;
      if(CurLineStateIndex==pCurLineState->LinesCount){
        pCurLine=pCurLine->pNext;
        if(pCurLine==NULL) StopFatalError(0,"TextLine overflow.");
        pCurLineState=pCurLine->GetLineState();
        CurLineStateIndex=0;
      }
    }
    
    pcurstr=pCurLineState->pLines[CurLineStateIndex].pstr;
  }
  
  {
    u32 tx=0;
    const u32 ty=2;
    const u32 th=glCanvasTextHeight;
    
    u32 idx=0;
    
    while(1){
      UnicodeChar wc=*pcurstr++;
      if(wc==0) break;
      if((DrawCursorFlag==true)&&(curpos==idx)){
        const u32 x=2+tx-1,y=ty,h=th;
        pcan->SetColor(RGB15(12,12,12)|BIT15);
        pcan->DrawLine(x+0,y,x+0,y+h);
        pcan->SetColor(RGB15(26,26,28)|BIT15);
        pcan->DrawLine(x+1,y,x+1,y+h);
      }
      if(wc!=WC_Enter){
        UnicodeChar tmp[2]={wc,0};
        const u32 tw=pTestFont->GetFontWidth(wc)+FontWidthMargin;
        if(WindowRectWidth<(tx+tw)) break;
        pcan->TextOutW(2+tx,ty+1,tmp);
        tx+=tw;
        }else{
        const u16 col=RGB15(8,24,8)|BIT15;
        u32 x=2+tx,y=ty+1;
        x+=3; y+=1;
        for(u32 idx=0;idx<8;idx++){
          pcan->SetPixel(x,y,col);
          y++;
        }
        pcan->SetPixel(x-2,y-2,col);
        pcan->SetPixel(x-1,y-1,col);
        pcan->SetPixel(x+2,y-2,col);
        pcan->SetPixel(x+1,y-1,col);
        pcan->SetPixel(x,y,col);
      }
      idx++;
    }
  }
}

#include "TextEdit_File.h"

u32 TextEdit_GetCursorPosition(void)
{
  return(CursorPos);
}

const CTextEditLine* TextEdit_GetTopLine(void)
{
  return(pTextEdit->pTopLine);
}

void TextEdit_ClearAllLineState(void)
{
  CTextEditLine *pCurLine=pTextEdit->pTopLine;
  
  while(1){
    pCurLine->LineState_Clear();
    pCurLine=pCurLine->pNext;
    if(pCurLine==NULL) break;
  }
}

#include "TextEdit_Clipboard.h"
