#include <EDK.h>
#include "General.h"


static void AddTreePath(HWND hwndTree, char* acPath) {
  HTREEITEM hItem = NULL;
  char* pc = strtok(acPath, "\\");
  while (pc != NULL) {
    HTREEITEM hParentItem = hItem;
    hItem = TreeView_GetChild(hwndTree, hItem);
    for (;;) {
      if (hItem == NULL) {
        TV_INSERTSTRUCT tvins;
        tvins.hParent = hParentItem;
        tvins.hInsertAfter = TVI_LAST;
        tvins.item.mask = TVIF_TEXT;
        tvins.item.pszText = pc;
        win(hItem = TreeView_InsertItem(hwndTree, &tvins));
        break;
      }
      char ac[256];
      TV_ITEM tvi;
      tvi.hItem = hItem;
      tvi.pszText = ac;
      tvi.cchTextMax = sizeof ac;
      tvi.mask = TVIF_HANDLE | TVIF_TEXT;
      win(TreeView_GetItem(hwndTree, &tvi));
      if (strcmp(ac, pc) == NULL) {
        break;
      }
      hItem = TreeView_GetNextSibling(hwndTree, hItem);
    }
    pc = strtok(NULL, "\\");
  }
}


static void ReadConfigFile(HWND hwndTree) {
  MappedFile config;
  config.SetName(GetProgramDirectory("Control.txt"));
  byte* pb = (byte*)memchr(config.GetStart(), 0, config.GetEnd() - config.GetStart());
  if (pb != NULL) {
    config.SetEnd(pb);
  }
  pb = (byte*)memchr(config.GetStart(), '[', config.GetEnd() - config.GetStart());
  while (pb != NULL) {
    if (pb == config.GetStart() || pb[-1] == '\n') {
      pb++;
      byte* pbEnd = (byte*)memchr(pb, ']', config.GetEnd() - pb);
      if (pbEnd != NULL) {
        char ac[256];
        assert(pbEnd - pb < sizeof ac);
        memcpy(ac, pb, pbEnd - pb);
        ac[pbEnd - pb] = 0;
        AddTreePath(hwndTree, ac);
      }
    } else {
      pb++;
    }
    pb = (byte*)memchr(pb, '[', config.GetEnd() - pb);
  }
}


static char* GetTreePath(HWND hwndTree, HTREEITEM hItem, char* pcStart, char* pcEnd) {
  HTREEITEM hParentItem = TreeView_GetParent(hwndTree, hItem);
  if (hParentItem != NULL) {
    pcStart = GetTreePath(hwndTree, hParentItem, pcStart, pcEnd);
    assert(pcStart < pcEnd);
    *pcStart++ = '\\';
  }
  TV_ITEM tvi;
  tvi.hItem = hItem;
  tvi.pszText = pcStart;
  tvi.cchTextMax = pcEnd - pcStart;
  tvi.mask = TVIF_HANDLE | TVIF_TEXT;
  win(TreeView_GetItem(hwndTree, &tvi));
  pcStart += strlen(pcStart);
  return pcStart;
}


static void SetTreePath(HWND hwndTree, char* pcPath) {
  HTREEITEM hParent = NULL;
  char* pc = strtok(pcPath, "\\");
  while (pc != NULL) {
    HTREEITEM hItem = TreeView_GetChild(hwndTree, hParent);
    while (hItem != NULL) {
      char ac[256];
      TV_ITEM tvi;
      tvi.hItem = hItem;
      tvi.pszText = ac;
      tvi.cchTextMax = sizeof ac;
      tvi.mask = TVIF_HANDLE | TVIF_TEXT;
      win(TreeView_GetItem(hwndTree, &tvi));
      if (strcmp(ac, pc) == 0) {
        hParent = hItem;
        break;
      }
      hItem = TreeView_GetNextSibling(hwndTree, hItem);
    }
    pc = strtok(NULL, "\\");
  }
  if (hParent != NULL) {
    win(TreeView_SelectItem(hwndTree, hParent));
  }
}


static void ReadSingleSetting(char* pcSetting, char* pcOld, char* pcNew, int iSizeNew) {
  try {
    char* pcSection = NULL;
    char* pcSlash = strrchr(pcSetting, '\\');
    if (pcSlash != NULL) {
      *pcSlash = 0;
      pcSection = pcSetting;
      pcSetting = pcSlash + 1;
    }
    char* pcNoNumber = pcOld;
    while (*pcNoNumber != 0) {
      if (!isdigit(*pcNoNumber)) {
        break;
      }
      pcNoNumber++;
    }
    if (pcNoNumber != pcOld && *pcNoNumber == 0) {
      int i = gconf.GetInt(pcSection, pcSetting, atoi(pcOld));
      int iLength = wsprintf(pcNew, "%d", i);
      assert(iLength < iSizeNew);
    } else {
      gconf.GetString(pcSection, pcSetting, pcNew, iSizeNew, pcOld);
    }
  } catch (...) {
    report();
  }
}


static byte* ReadSettings(byte* pbStart, byte* pbEnd) {
  byte* pb = (byte*)memchr(pbStart, '<', pbEnd - pbStart);
  while (pb != NULL) {
    if (pb == pbStart || pb[-1] == '\n') {
      if (pb < pbEnd - 4 && memcmp(pb, "<G> ", 4) == 0) {
        byte* pbNextLine = (byte*)memchr(pb, '\r', pbEnd - pb);
        if (pbNextLine == NULL) {
          pbNextLine = pbEnd;
        }
        byte* pbEqual = (byte*)memchr(pb, '=', pbNextLine - pb);
        if (pbEqual != NULL) {
          char acSetting[1024];
          int iLength = pbEqual - pb - 4;
          assert(iLength < sizeof acSetting);
          memcpy(acSetting, pb + 4, iLength);
          acSetting[iLength] = 0;
          char acOld[256];
          byte* pbValue = pbEqual + 1;
          int iOldLength = pbNextLine - pbValue;
          assert(iOldLength < sizeof acOld);
          memcpy(acOld, pbValue, iOldLength);
          acOld[iOldLength] = 0;
          char acNew[256];
          ReadSingleSetting(acSetting, acOld, acNew, sizeof acNew);
          int iNewLength = strlen(acNew);
          int iDelta = iNewLength - iOldLength;
          if (iDelta != 0) {
            memmove(pbValue + iNewLength, pbValue + iOldLength, pbEnd - pbValue - iOldLength);
            pbEnd += iDelta;
          }
          memcpy(pbValue, acNew, iNewLength);
        }
      }
    }
    pb++;
    pb = (byte*)memchr(pb, '<', pbEnd - pb);
  }
  return pbEnd;
}


static void ReadConfigText(HWND hwndEdit, char* pcSection) {
  SetWindowText(hwndEdit, "");
  int iLength = strlen(pcSection);
  if (pcSection[iLength - 1] == ' ') {
    SendMessage(hwndEdit, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FIXED_FONT), 0);
  } else {
    SendMessage(hwndEdit, WM_SETFONT, (WPARAM)GetStockObject(ANSI_VAR_FONT), 0);
  }
  MappedFile config;
  config.SetName(GetProgramDirectory("Control.txt"));
  const byte* pb = (byte*)memchr(config.GetStart(), '[', config.GetEnd() - config.GetStart());
  while (pb != NULL) {
    if (pb == config.GetStart() || pb[-1] == '\n') {
      pb++;
      if (memcmp(pb, pcSection, iLength) == 0 && pb[iLength] == ']') {
        byte* pbStart;
        verify((pbStart = (byte*)memchr(pb, '\n', config.GetEnd() - pb)) != NULL);
        pbStart++;
        const byte* pbEnd = pbStart;
        do {
          pbEnd++;
          pbEnd = (byte*)memchr(pbEnd, '[', config.GetEnd() - pbEnd);
          if (pbEnd == NULL) {
            pbEnd = config.GetEnd();
            break;
          }
        } while (pbEnd[-1] != '\n');
        while (pbEnd > pbStart + 1 && isspace(pbEnd[-1])) {
          pbEnd--;
        }
        int iSize = pbEnd - pbStart;
        byte abBuffer[65536];
        assert(iSize + 2 < sizeof abBuffer);
        memcpy(abBuffer, pbStart, iSize);
        abBuffer[iSize] = '\r';
        abBuffer[iSize + 1] = '\n';
        byte* pb = ReadSettings(abBuffer, abBuffer + iSize + 2);
        assert(pb - abBuffer < sizeof abBuffer);
        *pb = 0;
        SetWindowText(hwndEdit, (char*)abBuffer);
        return;
      }
    } else {
      pb++;
    }
    pb = (byte*)memchr(pb, '[', config.GetEnd() - pb);
  }
}


flag gfSettingsChanged;

static void WriteSingleSetting(char* pcSetting, char* pcNew) {
  try {
    char* pcSection = NULL;
    char* pcSlash = strrchr(pcSetting, '\\');
    if (pcSlash != NULL) {
      *pcSlash = 0;
      pcSection = pcSetting;
      pcSetting = pcSlash + 1;
    }
    char* pcNoNumber = pcNew;
    while (*pcNoNumber != 0) {
      if (!isdigit(*pcNoNumber)) {
        break;
      }
      pcNoNumber++;
    }
    if (pcNoNumber != pcNew && *pcNoNumber == 0) {
      int iNew = atoi(pcNew);
      int iOld = gconf.GetInt(pcSection, pcSetting, iNew);
      if (iOld != iNew) {
        gconf.SetInt(pcSection, pcSetting, iNew);
        gfSettingsChanged = true;
      }
    } else {
      char acOld[256];
      gconf.GetString(pcSection, pcSetting, acOld, sizeof acOld, pcNew);
      if (strcmp(acOld, pcNew) != 0) {
        gconf.SetString(pcSection, pcSetting, pcNew);
        gfSettingsChanged = true;
      }
    }
  } catch (...) {
    report();
  }
}


static void WriteSettings(const byte* pbStart, const byte* pbEnd) {
  byte* pb = (byte*)memchr(pbStart, '<', pbEnd - pbStart);
  while (pb != NULL) {
    if (pb == pbStart || pb[-1] == '\n') {
      if (pb < pbEnd - 4 && memcmp(pb, "<G> ", 4) == 0) {
        const byte* pbNextLine = (byte*)memchr(pb, '\r', pbEnd - pb);
        if (pbNextLine == NULL) {
          pbNextLine = pbEnd;
        }
        byte* pbEqual = (byte*)memchr(pb, '=', pbNextLine - pb);
        if (pbEqual != NULL) {
          char acSetting[1024];
          int iLength = pbEqual - pb - 4;
          assert(iLength < sizeof acSetting);
          memcpy(acSetting, pb + 4, iLength);
          acSetting[iLength] = 0;
          char acNew[256];
          byte* pbValue = pbEqual + 1;
          int iNewLength = pbNextLine - pbValue;
          assert(iNewLength < sizeof acNew);
          memcpy(acNew, pbValue, iNewLength);
          acNew[iNewLength] = 0;
          WriteSingleSetting(acSetting, acNew);
        }
      }
    }
    pb++;
    pb = (byte*)memchr(pb, '<', pbEnd - pb);
  }
}


static void WriteConfigText(HWND hwndEdit, char* pcSection) {

  // get new text from the edit control
  int iNewSize = SendMessage(hwndEdit, WM_GETTEXTLENGTH, 0, 0);
  if (iNewSize == 0) {
    return;
  }
  auto_ptr<byte> pbNew;
  win(pbNew = (byte*)malloc(iNewSize + 1));
  GetWindowText(hwndEdit, (char*)(byte*)pbNew, iNewSize + 1);
  while (iNewSize > 0 && isspace(pbNew[iNewSize - 1])) {
    iNewSize--;
  }

  // get position of the old text in the config file
  int iLength = strlen(pcSection);
  MappedFile config;
  config.SetName(GetProgramDirectory("Control.txt"));
  const byte* pb = (byte*)memchr(config.GetStart(), '[', config.GetEnd() - config.GetStart());
  while (pb != NULL) {
    if (pb == config.GetStart() || pb[-1] == '\n') {
      pb++;
      if (memcmp(pb, pcSection, iLength) == 0 && pb[iLength] == ']') {
        byte* pbOld;
        verify((pbOld = (byte*)memchr(pb, '\n', config.GetEnd() - pb)) != NULL);
        pbOld++;
        const byte* pbEnd = pbOld;
        do {
          pbEnd++;
          pbEnd = (byte*)memchr(pbEnd, '[', config.GetEnd() - pbEnd);
          if (pbEnd == NULL) {
            pbEnd = config.GetEnd();
            break;
          }
        } while (pbEnd[-1] != '\n');
        while (pbEnd > pbOld + 1 && isspace(pbEnd[-1])) {
          pbEnd--;
        }
        int iOldSize = pbEnd - pbOld;
        config.Replace(pbOld, iOldSize, pbNew, iNewSize);
        WriteSettings(pbNew, pbNew + iNewSize);
        return;
      }
    } else {
      pb++;
    }
    pb = (byte*)memchr(pb, '[', config.GetEnd() - pb);
  }
}


static BOOL CALLBACK ControlDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  try {
    static char gacSection[256];
    switch (uMsg) {
    case WM_INITDIALOG:
      {
        CenterWindow(hwnd);
        gacSection[0] = 0;
        ReadConfigFile(GetDlgItem(hwnd, IDC_Tree));
        char ac[256];
        gconf.GetString(NULL, "Control Center Selected Item", ac, sizeof ac, "");
        SetTreePath(GetDlgItem(hwnd, IDC_Tree), ac);
        return TRUE;
      }
    case WM_NOTIFY:
      if (((NMHDR*)lParam)->idFrom == IDC_Tree && ((NMHDR*)lParam)->code == TVN_SELCHANGED) {
        HWND hwndEdit = GetDlgItem(hwnd, IDC_Edit);
        if (SendMessage(hwndEdit, EM_GETMODIFY, 0, 0) != 0 && gacSection[0] != 0) {
          WriteConfigText(hwndEdit, gacSection);
          SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0);
        }
        GetTreePath(((NMHDR*)lParam)->hwndFrom, ((NM_TREEVIEW*)lParam)->itemNew.hItem, gacSection, gacSection + sizeof gacSection);
        ReadConfigText(GetDlgItem(hwnd, IDC_Edit), gacSection);
        SendDlgItemMessage(hwnd, IDC_Edit, EM_SETMODIFY, FALSE, 0);
      }
      break;
    case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDC_Edit:
        switch (HIWORD(wParam)) {
        case EN_SETFOCUS:
          {
            DWORD dwSel = SendDlgItemMessage(hwnd, IDC_Edit, EM_GETSEL, 0, 0);
            if (LOWORD(dwSel) != HIWORD(dwSel)) {
              SendDlgItemMessage(hwnd, IDC_Edit, EM_SETSEL, 0, 0);
              SendDlgItemMessage(hwnd, IDC_Edit, EM_SCROLLCARET, 0, 0);
            }
            break;
          }
        case EN_KILLFOCUS:
          {
            HWND hwndEdit = GetDlgItem(hwnd, IDC_Edit);
            if (SendMessage(hwndEdit, EM_GETMODIFY, 0, 0) != 0 && gacSection[0] != 0) {
              WriteConfigText(hwndEdit, gacSection);
              SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0);
            }
            break;
          }
        }
        break;
      case IDCANCEL:
        {
          char ac[256];
          HWND hwndTree = GetDlgItem(hwnd, IDC_Tree);
          GetTreePath(hwndTree, TreeView_GetSelection(hwndTree), ac, ac + sizeof ac);
          gconf.SetString(NULL, "Control Center Selected Item", ac);
          EndDialog(hwnd, 0);
          return TRUE;
        }
      }
    }
  } catch (...) {
    report();
  }
  return FALSE;
}


flag Control() {
  gfSettingsChanged = false;
  extern void StopFullscreen(HWND hwnd);
  StopFullscreen(ghwnd);
  DialogBox(ghinst, MAKEINTRESOURCE(IDD_Control), ghwnd, ControlDlgProc);
  extern void ResumeFullscreen(HWND hwnd);
  ResumeFullscreen(ghwnd);
  return gfSettingsChanged;
}
