// kite.cc - implements main application class, kite_app.
//    Copyright (C) 2000-2002 Laurynas Biveinis <lauras@softhome.net>
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

#include "global.h"
#include "aexecupd.h"
#include "kite.h"
#include "packages.h"

#define Uses_MsgBox
#define Uses_TButton
#define Uses_TCommandSet
#define Uses_TDialog
#define Uses_TEventQueue
#define Uses_TInputLine
#define Uses_TLabel
#define Uses_TRadioButtons
#define Uses_TRect
#define Uses_TSItem
#define Uses_TStaticText
#define Uses_TWindow
#include <rhtvision/tv.h>

#include <dir.h>
#include <dos.h>
#include <dpmi.h>
#include <errno.h>
#include <go32.h>
#include <fstream.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/exceptn.h>
#include <sys/stat.h>

#ifdef HAVE_POPT_H
#include <popt.h>
#endif

static bool is_win9x_dos_box(void) __attribute__((const));

kite_app::kite_app(int ac, char *av[]) :
    TProgInit(&kite_app::initStatusLine,
              &kite_app::initMenuBar,
              &kite_app::initDeskTop
             ),
    log(NULL),
    argc(ac),
    argv(av)
{
    process_options(log_fn, pkg_dir, djgpp_dir, &disable_tails, &restore_tails);
    log = new log_file(log_fn);
    log->print_log("KITE version 0.5");
    message(this, evCommand, cmStartInstallation, NULL);
}
//-----------------------------------------------------------------------------
kite_app::~kite_app(void)
{
    delete log;
#ifdef MSS
    MSS_LOG_BLOCK_LIST;
#endif
}
//-----------------------------------------------------------------------------
bool kite_app::check_for_reg_key(const char * key, const char * value)
{
   char * fname = new char[L_tmpnam + 1];
   tmpnam(fname);
   char command[500] = "regedit /e ";
   strcat(command, fname);
   strcat(command, " ");
   strcat(command, key);
   if (exec_command(command, "Checking registry...") == -1)
      errno_box("Registry check (launching regedit) failed: %s");
   // I'm not sure but we must wait until regedit process finishes.
   // We check it by waiting for output file to appear.
   struct stat finfo;
   do
   {
      stat(fname, &finfo);
   }
   while (!finfo.st_size);
   char * buf = new char[finfo.st_size];
   ifstream * f = new ifstream(fname);
   f->read(buf, finfo.st_size);
   f->close();
   ::remove(fname);
   delete [] fname;
   bool found = (strstr(buf, value)) ? true : false;
   delete [] buf;
   return found;
}
//-----------------------------------------------------------------------------
void kite_app::do_installation(void)
{
    greetings();
    if ((_osmajor == 7) && num_tails_enabled())
       update_registry();
    char djdir[PATH_MAX + 1];
    get_dir(new djgpp_dir_dialog, djdir);
    log->print_log("DJGPP directory is %s", djdir);
    if (mkdir(djdir, S_IWUSR) && (errno != EEXIST))
       errno_box("DJGPP directory creation failed: %s");
    init_pakke(djdir);
    char zip_dir[PATH_MAX + 1];
    get_dir(new zip_path_dialog, zip_dir);
    install_packages(djdir, zip_dir);
    if (!num_tails_enabled())
       restore_num_tails();
    update_autoexec_bat(djdir);
    destroy(this);
    exit(0);
}
//-----------------------------------------------------------------------------
void kite_app::errno_box(const char * message)
{
    char error_buf[300];
    sprintf(error_buf, message, strerror(errno));
    log->print_log(error_buf);
    messageBox(error_buf, mfError | mfOKButton);
    log->print_log("Exiting.");
    destroy(this);
    exit(3);
    return;
}
//-----------------------------------------------------------------------------
int kite_app::exec_command(const char * command, const char * message)
{
   wait_box * wait_dialog = new wait_box(message);
   TEventQueue::suspend();
   __djgpp_exception_toggle();
   int exit_code = system(command);
   __djgpp_exception_toggle();
   TEventQueue::resume();
   delete wait_dialog;
   return exit_code;
}
//-----------------------------------------------------------------------------
void kite_app::exit_request(const int message)
{
    if (message == cmCancel)
    {
       log->print_log("Exiting");
       destroy(this);
       exit(2);
    }
}
//-----------------------------------------------------------------------------
void kite_app::get_dir(path_dialog * dialog, char * dir)
{
    bool ok = dialog->get_path(dir);
    if (!is_path_separator(dir[strlen(dir)]))
       strcat(dir, "/");
    destroy(dialog);
    if (!ok)
       exit_request(cmCancel);
}
//-----------------------------------------------------------------------------
void kite_app::greetings(void)
{
    TDialog * dialog = new TDialog(TRect(0, 0, 57, 14), "Welcome to DJGPP");
    dialog->options |= ofCentered;
    dialog->insert(new TStaticText(TRect(5, 1, 55, 2),
        "Welcome to KITE, the DJGPP installer!"));
    dialog->insert(new TStaticText(TRect(2, 3, 55, 5),
        "This program will help you to install DJGPP, the \n"
        "complete C, C++, etc. development system for DOS."));
    dialog->insert(new TStaticText(TRect(2, 5, 48, 6),
        "Now press \"Next\" button below to continue."));
    dialog->insert(new TStaticText(TRect(2, 6, 55, 9),
        "You can press \"Exit\" button at any time to abort \n"
        "installation process."));
    dialog->insert(new TStaticText(TRect(2, 9, 55, 10),
        "Copyright (C) Laurynas Biveinis <lauras@softhome.net>"));
    dialog->insert(new TButton(TRect(15, 11, 27, 13), "~N~ext", cmOK, bfDefault));
    dialog->insert(new TButton(TRect(30, 11, 42, 13), "~E~xit", cmCancel, bfNormal));
    dialog->selectNext(False);
    int return_code = TProgram::deskTop->execView(dialog);
    destroy(dialog);
    exit_request(return_code);
}
//-----------------------------------------------------------------------------
void kite_app::handleEvent(TEvent& event)
{
   TApplication::handleEvent(event);
   if ((event.what == evCommand) &&
       (event.message.command == cmStartInstallation))
      do_installation();
}
//-----------------------------------------------------------------------------
// We have to redefine this because we don't have status line and want to
// cover its area.
TDeskTop *kite_app::initDeskTop(TRect r)
{
    return new TDeskTop(r);
}
//-----------------------------------------------------------------------------
TMenuBar *kite_app::initMenuBar(TRect r)
{
    (void)r; // Imagine that we do use this variable
    return NULL;
}
//-----------------------------------------------------------------------------
TStatusLine *kite_app::initStatusLine(TRect r)
{
    (void)r;
    return NULL;
}
//-----------------------------------------------------------------------------
void kite_app::init_pakke(const char * dir)
{
    char command[300] = "pakke --initdb --with-pakke --root ";
    strcat(command, dir);
    strcat(command, " > /dev/null");
    log->print_log("Initializing pakke with command %s", command);
    if (exec_command(command, "Initializing pakke...") == -1)
       errno_box("Launching pakke.exe failed: %s");
}
//-----------------------------------------------------------------------------
void kite_app::install_packages(const char * dj_dir, const char * zip_dir)
{
   pkg_list * packages = new pkg_list(dj_dir, zip_dir, *log);
   packages->install();
   delete packages;
}
//-----------------------------------------------------------------------------
void kite_app::merge_registry_file(const char * file)
{
   char command[300] = "regedit ";
   strcat(command, file);
   if (exec_command(command, "Updating registry...") == -1)
      errno_box("Registry update (launching regedit) failed: %s");
}
//-----------------------------------------------------------------------------
bool kite_app::num_tails_enabled(void)
{
   return check_for_reg_key(
         "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\control\\FileSystem",
         "\"NameNumericTail\"=hex:01");
}
//-----------------------------------------------------------------------------
void kite_app::process_options(char * log_file, char * pkg_dir, 
                               char * djgpp_dir, int * tails, int * post_tails)
{
   int  no_log = 0;
   int  disable_tails = 0, enable_tails = 0;
   int  restore_tails = 0, leave_tails = 0;

   /* static */ struct poptOption options[] = {
     {"disable-logging",   'q',  POPT_ARG_NONE,   &no_log,   0, NULL, NULL},
     {"log-file",          'l',  POPT_ARG_STRING, log_file,  0, NULL, NULL},
     {"pkg-dir",           'p',  POPT_ARG_STRING, pkg_dir,   0, NULL, NULL},
     {"djgpp-dir",         'd',  POPT_ARG_STRING, djgpp_dir, 0, NULL, NULL},
     {"disable-tails",   'x', POPT_ARG_NONE, &disable_tails, 0, NULL, NULL},
     {"keep-tails",       'k', POPT_ARG_NONE, &enable_tails, 0, NULL, NULL},
     {"restore-tails",   'r', POPT_ARG_NONE, &restore_tails, 0, NULL, NULL},
     {"no-restore-tails",  'n', POPT_ARG_NONE, &leave_tails, 0, NULL, NULL},
     {NULL,                '\0',  0,              NULL,      0, NULL, NULL}
   };
   log_file[0] = '\0';
   pkg_dir[0] = '\0';
   djgpp_dir[0] = '\0';
   poptContext con = poptGetContext("KITE", argc, argv, options, 0);
   poptGetNextOpt(con);
   // Disable logging by logging to /dev/null.
   if (no_log)
      strcpy(log_file, "/dev/null");
   // See what to do about numeric tails.
   *tails = disable_tails ? tails_off : (enable_tails ? tails_on : ask_user);
   *post_tails = restore_tails ? tails_on : (leave_tails ? tails_off : ask_user);
   poptFreeContext(con);
}
//-----------------------------------------------------------------------------
void kite_app::restore_num_tails(void)
{
   TDialog * dialog = new TDialog(TRect(0, 0, 60, 11), "Enable Numeric Tails");
   dialog->options |= ofCentered;
   dialog->insert(new TStaticText(TRect(4, 2, 58, 8),
               "You have disabled numeric tails in short file names.\n"
               "That was OK for DJGPP installation but due to Windows\n"
               "problems it can lead to data loss in the future.\n \n"
               "   Do you want to re-enable numeric tails now?"));
   dialog->insert(new TButton(TRect(10, 8, 20, 10), "~Y~es", cmYes, bfDefault));
   dialog->insert(new TButton(TRect(25, 8, 35, 10), "~N~o", cmNo, bfNormal));
   dialog->insert(new TButton(TRect(40, 8, 50, 10), "~E~xit", cmCancel, bfNormal));
   dialog->selectNext(False);
   int exit_code = deskTop->execView(dialog);
   destroy(dialog);
   exit_request(exit_code);
   if (exit_code == cmYes)
   {
      log->print_log("Enabling numeric tails in registry");
      merge_registry_file("onnumtl.reg");
   }
}
//-----------------------------------------------------------------------------
void kite_app::update_registry(void)
{
    int dlg_exit;
    TDialog * dialog = new TDialog(TRect(0, 0, 60, 12), "Question");
    dialog->options |= ofCentered;
    dialog->insert(new TStaticText(TRect(2, 1, 58, 6),
       "If you are going to use DJGPP in both DOS box under\n"
       "Windows and in plain DOS mode, this program will have\n"
       "to update Windows registry to disable numeric tails in\n"
       "short file names (See the DJGPP FAQ 22.19 for details).\n"
       "However, this will require additional reboot."));
    dialog->insert(new TStaticText(TRect(2, 7, 58, 8),
       "    So, are you going to use DJGPP in both ways?"));
    dialog->insert(new TButton(TRect(2, 9, 18, 11), "~Y~es", cmYes, bfNormal));
    dialog->insert(new TButton(TRect(22, 9, 38, 11), "~N~o", cmNo, bfDefault));
    dialog->insert(new TButton(TRect(42, 9, 58, 11), "~E~xit", cmCancel, bfNormal));
    dialog->selectNext(False);
    dlg_exit = TProgram::deskTop->execView(dialog);
    destroy(dialog);
    exit_request(dlg_exit);
    if (dlg_exit == cmYes)
    {
       log->print_log("Disabling numeric tails in registry");
       merge_registry_file("nonumtl.reg");
       if (is_win9x_dos_box())
          messageBox("Now please restart your computer and rerun KITE.",
                     mfInformation | mfOKButton);
       else
          messageBox("Now please start Windows and run KITE from there.",
                     mfInformation | mfOKButton);
       destroy(this);
       exit(1);
    }
}
//-----------------------------------------------------------------------------
void kite_app::update_autoexec_bat(const char * dir)
{
   int exit_code;
   log->print_log("Updating autoexec.bat");
   autoexec_update * update_dialog = new autoexec_update;
   exit_code = update_dialog->update(dir);
   destroy(update_dialog);
   exit_request(exit_code);
}

wait_box::wait_box(const char * message)
{
   TRect bounds = TProgram::deskTop->getExtent();
   int mes_len = strlen(message);
   dialog = new TDialog(TRect((bounds.b.x - mes_len - 4) / 2, 8,
                              (bounds.b.x + mes_len + 4) / 2, 13),
                        "Please Wait");
   dialog->insert(new TStaticText(TRect(2, 2, 2 + mes_len, 3), message));
   TProgram::deskTop->lock();
   TProgram::deskTop->insert(dialog);
   TProgram::deskTop->unlock();
}

wait_box::~wait_box(void)
{
   TProgram::deskTop->remove(dialog);
   destroy(dialog);
}

// Helper functions

bool is_win9x_dos_box(void)
{
   __dpmi_regs registers;
   registers.x.ax = 0x7147;
   registers.h.dl = 0x00;
   registers.x.ds = __tb >> 4;
   registers.x.si = __tb & 0x0F;
   __dpmi_int(0x21, &registers);
   return (_osmajor == 7) && (registers.x.ax != 0x7100);
}

