/* -*-C++-*-    $NetBSD: hpcmenu.cpp,v 1.19 2010/04/06 16:20:27 nonaka Exp $    */

/*-
* Copyright (c) 2001, 2004 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by UCHIYAMA Yasushi.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

#include <hpcmenu.h>
#include <hpcboot.h>
#include <res/resource.h>
#include <menu/window.h>
#include <menu/tabwindow.h>
#include <menu/rootwindow.h>
#include <menu/menu.h>
#include <machine/bootinfo.h>
#include <framebuffer.h>
#include <console.h>
#include <string.h>

HpcMenuInterface *HpcMenuInterface::_instance = 0;

HpcMenuInterface &
HpcMenuInterface::Instance()
{

       if (!_instance)
               _instance = new HpcMenuInterface();
       return *_instance;
}

void
HpcMenuInterface::Destroy()
{

       if (_instance)
               delete _instance;
}

HpcMenuInterface::HpcMenuInterface()
{

       if (!load())
               _set_default_pref();
       _pref._version  = HPCBOOT_VERSION;
       _pref._size     = sizeof(HpcMenuPreferences);

       _cons_parameter = 0;
       memset(_cons_hook, 0, sizeof(struct cons_hook_args) * 4);
       memset(&_boot_hook, 0, sizeof(struct boot_hook_args));
}

void
HpcMenuInterface::print(TCHAR *buf)
{

       if (_console)
               _console->print(buf);
}

void
HpcMenuInterface::get_options()
{

       _main->get();
       _option->get();
}

TCHAR *
HpcMenuInterface::dir(int i)
{
       int res = IDS_DIR_RES(i);

       if (!IDS_DIR_RES_VALID(res))
               return 0;

       if (_pref.dir_user && res == IDS_DIR_USER_DEFINED) {
               return _pref.dir_user_path;
       }

       TCHAR *s = reinterpret_cast <TCHAR *>
           (LoadString(_root->_app._instance, res, 0, 0));

       return s;
}

int
HpcMenuInterface::dir_default()
{

       return _pref.dir_user ? IDS_DIR_SEQ(IDS_DIR_USER_DEFINED) : 0;
}

void
HpcMenuInterface::_set_default_pref()
{

       _pref._magic            = HPCBOOT_MAGIC;
       _pref.dir               = 0;
       _pref.dir_user          = FALSE;
       _pref.kernel_user       = FALSE;
       _pref.platid_hi         = 0;
       _pref.platid_lo         = 0;
       _pref.rootfs            = 0;
       wsprintf(_pref.rootfs_file, TEXT("miniroot.fs"));
       _pref.boot_serial       = FALSE;
       _pref.boot_verbose      = FALSE;
       _pref.boot_single_user  = FALSE;
       _pref.boot_ask_for_name = FALSE;
       _pref.boot_debugger     = FALSE;
       wsprintf(_pref.boot_extra, TEXT(""));
       _pref.auto_boot         = 0;
       _pref.reverse_video     = FALSE;
       _pref.pause_before_boot = TRUE;
       _pref.safety_message    = TRUE;
#ifdef MIPS
       _pref.serial_speed      = 9600; // historical reason.
#else
       _pref.serial_speed      = 19200;
#endif
}

//
// load and save current menu status.
//
BOOL
HpcMenuInterface::load()
{
       TCHAR path[MAX_PATH];

       if (!_find_pref_dir(path))
               return FALSE;

       TCHAR filename[MAX_PATH];
       wsprintf(filename, TEXT("\\%s\\hpcboot.cnf"), path);
       HANDLE file = CreateFile(filename, GENERIC_READ, 0, 0, OPEN_EXISTING,
           FILE_ATTRIBUTE_NORMAL, 0);
       if (file == INVALID_HANDLE_VALUE)
               return FALSE;

       DWORD cnt;
       // read header.
       if (!ReadFile(file, &_pref, 12, &cnt, 0))
               goto bad;
       if (_pref._magic != HPCBOOT_MAGIC)
               goto bad;
       // read all.
       SetFilePointer(file, 0, 0, FILE_BEGIN);
       if (!ReadFile(file, &_pref, _pref._size, &cnt, 0))
               goto bad;
       CloseHandle(file);

       return TRUE;
bad:
       CloseHandle(file);
       return FALSE;
}

BOOL
HpcMenuInterface::save()
{
       TCHAR path[MAX_PATH];

       if (_find_pref_dir(path)) {
               TCHAR filename[MAX_PATH];
               wsprintf(filename, TEXT("\\%s\\hpcboot.cnf"), path);
               HANDLE file = CreateFile(filename, GENERIC_WRITE, 0, 0,
                   CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
               DWORD cnt;
               WriteFile(file, &_pref, _pref._size, &cnt, 0);
               CloseHandle(file);
               return cnt == _pref._size;
       }

       return FALSE;
}

// arguments for kernel.
int
HpcMenuInterface::setup_kernel_args(vaddr_t v, paddr_t p, size_t sz)
{
       int argc = 0;
       kaddr_t *argv = reinterpret_cast <kaddr_t *>(v);
       char *loc = reinterpret_cast <char *>
           (v + sizeof(char **) * MAX_KERNEL_ARGS);
       paddr_t locp = p + sizeof(char **) * MAX_KERNEL_ARGS;
       size_t len;
       TCHAR *w;
       char *ptr;

#define SETOPT(c)                                                       \
__BEGIN_MACRO                                                           \
       argv[argc++] = ptokv(locp);                                     \
       *loc++ =(c);                                                    \
       *loc++ = '\0';                                                  \
       locp += 2;                                                      \
__END_MACRO
       // 1st arg is kernel name.
//      DPRINTF_SETUP();  //if you want to use debug print, enable this line.

       w = _pref.kernel_user_file;
       argv[argc++] = ptokv(locp);
       len = WideCharToMultiByte(CP_ACP, 0, w, wcslen(w), 0, 0, 0, 0);
       WideCharToMultiByte(CP_ACP, 0, w, len, loc, len, 0, 0);
       loc[len] = '\0';
       loc += len + 1;
       locp += len + 1;

       if (_pref.boot_serial)          // serial console
               SETOPT('h');
       if (_pref.boot_verbose)         // boot verbosely
               SETOPT('v');
       if (_pref.boot_single_user)     // boot to single user
               SETOPT('s');
       if (_pref.boot_ask_for_name)    // ask for file name to boot from
               SETOPT('a');
       if (_pref.boot_debugger)        // break into the kernel debugger
               SETOPT('d');

       // boot from
       switch(_pref.rootfs) {
       case 0: // wd0
               break;
       case 1: // sd0
               argv[argc++] = ptokv(locp);
               strncpy(loc, "b=sd0", 6);
               loc += 6;
               locp += 6;
               break;
       case 2: // memory disk
               w = _pref.rootfs_file;
               argv[argc++] = ptokv(locp);
               strncpy(loc, "m=", 2);
               loc += 2;
               len = WideCharToMultiByte(CP_ACP, 0, w, wcslen(w), 0, 0, 0, 0);
               WideCharToMultiByte(CP_ACP, 0, w, len, loc, len, 0, 0);
               loc[len] = '\0';
               loc += len + 1;
               locp += 2 + len + 1;
               break;
       case 3: // nfs
               argv[argc++] = ptokv(locp);
               strncpy(loc, "b=nfs", 6);
               loc += 6;
               locp += 6;
               break;
       case 4: // dk0
               argv[argc++] = ptokv(locp);
               strncpy(loc, "b=dk0", 6);
               loc += 6;
               locp += 6;
               break;
       case 5: // ld0
               argv[argc++] = ptokv(locp);
               strncpy(loc, "b=ld0", 6);
               loc += 6;
               locp += 6;
               break;
       }

       // Extra kernel options. (Option tab window)
       w = _pref.boot_extra;
       len = WideCharToMultiByte(CP_ACP, 0, w, wcslen(w), 0, 0, 0, 0);

       if ((ptr = (char *)malloc(len)) == NULL) {
               MessageBox(_root->_window,
                   L"Can't allocate memory for extra kernel options.",
                   TEXT("WARNING"),
                   MB_ICONWARNING | MB_OK);
               UpdateWindow(_root->_window);

               return argc;
       }
       WideCharToMultiByte(CP_ACP, 0, w, len, ptr, len, 0, 0);
       ptr[len]='\0';

       while (*ptr == ' ' || *ptr == '\t')
               ptr++;
       while (*ptr != '\0') {
               len = strcspn(ptr, " \t");
               if (len == 0)
                       len = strlen(ptr);

               if (argc == MAX_KERNEL_ARGS || locp + len + 1 > p + sz) {
                       MessageBox(_root->_window,
                           L"Too many extra kernel options.",
                           TEXT("WARNING"),
                           MB_ICONWARNING | MB_OK);
                       UpdateWindow(_root->_window);
                       break;
               }
               argv[argc++] = ptokv(locp);
               strncpy(loc, ptr, len);
               loc[len] = '\0';
               loc += len + 1;
               locp += len + 1;

               ptr += len;
               while (*ptr == ' ' || *ptr == '\t')
                       ptr++;
       }

       return argc;
}

// kernel bootinfo.
void
HpcMenuInterface::setup_bootinfo(struct bootinfo &bi)
{
       FrameBufferInfo fb(_pref.platid_hi, _pref.platid_lo);
       TIME_ZONE_INFORMATION tz;
       DWORD tzid = GetTimeZoneInformation(&tz);

       memset(&bi, 0, sizeof(struct bootinfo));
       bi.length               = sizeof(struct bootinfo);
       bi.reserved             = 0;
       bi.magic                = BOOTINFO_MAGIC;
       bi.fb_addr              = fb.addr();
       bi.fb_type              = fb.type();
       bi.fb_line_bytes        = fb.linebytes();
       bi.fb_width             = fb.width();
       bi.fb_height            = fb.height();
       bi.platid_cpu           = _pref.platid_hi;
       bi.platid_machine       = _pref.platid_lo;
       bi.timezone             = tz.Bias;
       if (tzid == TIME_ZONE_ID_DAYLIGHT)
               bi.timezone    += tz.DaylightBias;
}

// Progress bar
void
HpcMenuInterface::progress(const char *msg)
{

       _root->progress(msg);
}

void
HpcMenuInterface::unprogress()
{

       _root->unprogress();
}

// Boot kernel.
void
HpcMenuInterface::boot()
{
       struct support_status *tab = _unsupported;
       uint32_t cpu = _pref.platid_hi;
       uint32_t machine = _pref.platid_lo;

       if (_pref.safety_message)
               for (; tab->cpu; tab++) {
                       if (tab->cpu == cpu && tab->machine == machine) {
                               MessageBox(_root->_window,
                                   tab->cause ? tab->cause :
                                   L"Not supported yet.",
                                   TEXT("BOOT FAILED"),
                                   MB_ICONERROR | MB_OK);
                               return;
                       }
               }

       if (_boot_hook.func)
               _boot_hook.func(_boot_hook.arg);
}