/**
 * Name: PATH manager
 * Author: Sergey Kopeliovich (Burunduk30@gmail.com)
 * Version: 3.09
 * Modifications:
 *   2010-12-25 - (+) asserts 
 *   2010-12-25 - (+) menu [2.01]
 *   2010-12-26 - Little fixes. Testing is complete. [2.02]
 *   2010-12-26 - (+) now that it can manage any variable (PATH, INCLUDE, LIB, ...) [3.00]
 *   2011-09-28 - (+) use WINAPI, read VAR before first "DrawMenu", little refactoring [3.01]
 *   2011-09-30 -
 *     (+) change variable without interaction with user (using command line options) [3.02]
 *     (+) the first try to use WINAPI SetEnvironmentVariable (unsuccessful yet) and to broadcast information "variables where modified" to all windows (also unsuccessful yet) [3.03]
 *     (+) get/add variable both from/to "user variables" and "system variables" [3.04]
 *     (+) ADD to tthe first position (not to the last as it was before) [3.05]
 *   2011-10-03 - 
 *     (+) if Execute(command) failed, just output WARNING, do not throw Exception [3.06]
 *     (+) colored output [3.07]
 *   2012-01-16 - 
 *     (+) output MessageBox iff execution was successful [3.08]
 *     (+) problem with russian names was fix [3.09]
 */

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cassert>

#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <algorithm>

#include <windows.h>

using namespace std;

#define forn(i, n) for (int i = 0; i < (int)(n); i++)
#define pb push_back
#define sz(a) (int)(a).size()

#define LEN(a) (int)(sizeof(a) / sizeof(a[0]))

typedef unsigned char byte;

#define TMP_FILE_1 "tmp.1"
#define TMP_FILE_2 "tmp.2"

const unsigned buf_len = 200;
char buf[buf_len];
const char *VAR = "PATH"; // default variable to process

/* First one is to access "user variable", the second one is to change "system variable" */
string paths[] = {"HKCU\\Environment", "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\Environment"};

vector <string> path;

void textColor( int color )
{
  HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(handle, color);
}

void colorPrintf( int color, const char *format, ... )
{
  textColor(color);

  va_list ap;
  va_start(ap, format);
  vprintf(format, ap);
  textColor(7); // default color
}

void Execute( const char *com )
{
  printf("Execute: %s\n", com);
  if (system(com))
    colorPrintf(2, "WARNING: can not execute system()\n");
}

void Exit( int code )
{
  printf("EXIT(%d)", code);
  exit(code);
}
void ExitOK() { Exit(0); }
void ExitSaved()
{
  MessageBox(NULL, "Для завершения установки:\nМой Компьютер --> Свойства --> Дополнительно --> Переменные среды --> Нажмите OK --> Запустите FAR так: Windows+R far Enter", "Sucess!", MB_OK);
  Exit(0);
}

string Cor( string str )
{
  char *s = (char *)str.c_str();
  int len = strlen(s) - 1;
  while (len >= 0 && ((byte)s[len] <= 32 || s[len] == '"'))
    len--;
  s[++len] = 0;
  while (s[0] == '"' || s[0] == ' ')
    s++, len--;
  while (len > 0 && (byte)*s <= 32)
    s++, len--;

  string res;
  forn(i, len)
    if (!(i && s[i] == '\\' && s[i - 1] == '\\') && !(s[i] == '\\' && !s[i + 1]))
      res += tolower(s[i]);
  return res;
}

bool AddToVar( string s )
{
  s = Cor(s);
  if (s == "")
    return false;
  forn(i, sz(path))
    if (path[i] == s)
      return false;
  printf("AddToVar: '%s'\n", s.c_str());
  path.push_back(s);
  return true;
}

void ShiftVarToTheFirstPosition( string s )
{
  s = Cor(s);
  if (s == "")
    return;
  forn(i, sz(path))
    if (path[i] == s)
    {
      rotate(path.begin(), path.begin() + i, path.begin() + i + 1);
      puts("ShiftVarToTheFirstPosition: OK");
      return;
    }
}

bool AddToVarBegin( string s )
{
  bool res = AddToVar(s);
  ShiftVarToTheFirstPosition(s);
  return res;
}

/* Use util "reg.exe" */
string GetVARfromRegister1()
{
  FILE *F;

  string s = "";
  forn(t, 2)
  {
    sprintf(buf, "reg query \"%s\" /v %s > %s", paths[t].c_str(), VAR, TMP_FILE_1);
    Execute(buf);
    sprintf(buf, "add_path_parse.cmd %s > %s", TMP_FILE_1, TMP_FILE_2);
    Execute(buf);
    assert(F = fopen(TMP_FILE_2, "rt"));

    int c;
    while ((c = fgetc(F)) != -1)
      s += c;
    fclose(F);
    Execute("del " TMP_FILE_2);

    s += ";";
  }

  puts("GetVARfromRegister1 : OK");
  return s;
}

/* Use WINAPI */
string GetVARfromRegister2()
{
  static char str[(int)1e5];

  int res = GetEnvironmentVariable(VAR, str, sizeof(str));
  assert(res == (int)strlen(str));
  return string(str);
}

void ReadVAR()
{
  string var = GetVARfromRegister2();

  path.clear();
  string s = "";
  forn(i, sz(var))
  {
    int c = var[i];
    if (c == ';')
      AddToVar(s), s = "";
    else if (c != 10 && c != 13)
      s += c;
  }
  puts("READ: OK");
}

void ViewVAR()
{
  printf("%s: %d parts\n", VAR, sz(path));
  forn(i, sz(path))
    printf("%2d : '%s'\n", i, path[i].c_str());
  puts("ViewVAR : OK");
}

char *ReadStr()
{
  static char s[buf_len], *res;
  do
  {
    assert(fgets(s, buf_len, stdin));
    res = s;
    while (*res && (byte)*res <= 32)
      res++;
  } while (!res[0]);
  return res;
}

void AddDir()
{
  char *res = ReadStr();
  if (!AddToVarBegin(res))
    printf("'%s' is already in VAR.\n", Cor(res).c_str());
}

/* Use util "reg.exe" */
void WriteVarToRegister1( const string &s )
{
  // cout << "s = " << s << endl;
  forn(t, 2)
  {
    stringstream ss;
    ss << "reg add \"" << paths[t] << "\" /v " << VAR << " /t REG_SZ /d \"" << s << "\" /f";
    Execute(ss.str().c_str());
  }
  puts("WriteVarToRegister1: OK");
}

/* Use WINAPI (only current...*sad*) */
void WriteVarToTheCurrentProcess( const string &s )
{
  forn(t, 2)
  {
    string tmp = (paths[t] + "\\" + string(VAR));
    cout << tmp << endl;
    assert(SetEnvironmentVariable(tmp.c_str(), s.c_str()) != 0);
  }
  puts("WriteVarToTheCurrentProcess: OK");
}

void WriteVAR()
{
  stringstream ss;
  forn(i, sz(path))
    ss << path[i] << ';';

  WriteVarToRegister1(ss.str());

/*
  WriteVarToTheCurrentProcess(ss.str());

  DWORD dwReturnValue;
  assert(SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 100 , &dwReturnValue) != 0);
*/

  puts("WriteVAR : OK");
}

/***
 * Main Menu (draw and interact)
 */

typedef void (*func)( void );

struct MenuItem
{
  string Title;
  func Op;

  MenuItem( string a, func b ) : Title(a), Op(b) { }
};

void DrawMenu();

MenuItem menu[] =
{
  MenuItem("Exit", ExitOK),
  MenuItem("View menu", DrawMenu),
  MenuItem("View VAR", ViewVAR),
  MenuItem("Read VAR from Windows Registry", ReadVAR),
  MenuItem("Add directory to VAR", AddDir),
  MenuItem("Save VAR to Windows Registry", WriteVAR),
};

void DrawMenu()
{
  printf("\n-----> VAR = %s\n", VAR);
  forn(i, LEN(menu))
    printf("%d - %s\n", i, menu[i].Title.c_str());
}

int main( int argc, char *argv[] )
{
  system("cls");
  forn(i, 20)
    puts("");

  colorPrintf(2, "Hello.\n");
  puts(" * Name: PATH manager");
  puts(" * Author: Sergey Kopeliovich (Burunduk30@gmail.com)");
  puts(" * Version: 3.07");
  printf(" * Last update: %s", __DATE__);
  puts("");
  printf("Usage: [variable] [command]\n");
  printf("  default variable = %s\n", VAR);
  puts("");
  puts("command = add <directory>");
  puts("");
  if (argc > 1)
    VAR = argv[1];
  if (argc > 2)
  {
    string command(argv[2]);
    if (argc < 4)
      printf("Unknown control sequence: command=%s with no arguments\n", command.c_str());
    else if (command == "add")
    {
      ReadVAR();
      AddToVarBegin(argv[3]);
      WriteVAR();
      ExitSaved();
    }
    else
      printf("Unknown control sequence: command=%s with argument=%s\n", command.c_str(), argv[3]);
    Exit(1);
  }

  ReadVAR();
  DrawMenu();

  while (1)
  {
    puts("");
    printf("Your choise: ");

    char *s = ReadStr();
    int ch, res = sscanf(s, "%d", &ch);
    puts("");
    if (res == 1 && 0 <= ch && ch < LEN(menu))
      menu[ch].Op();
    else
      puts("Invalid choise =(");
  }
  return 0;
}
