{*
 * This application launches the dmdirc java-based installer.
 *
 * DMDirc - Open Source IRC Client
 * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes,
 * Michael Nixon
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *}

(* Current DMDirc windows setup flow:
 *
 * 1) Outer wrapper EXE that extracts a 7zip SFX to windows temp dir
 * 2) 7zip SFX unpacks
 * 3) Wrapper EXE starts Setup.exe (this program)
 * 4) Setup checks for existence of the JRE and offers to download/install it
 * 5) Setup starts the java portion of the DMDirc installer
 *)
program Setup;

// Resource file - icon, versioninfo, manifest
{$R most.res}

{ ---------------------------------------------------------------------------- }
{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

// Use this instead of {$APPTYPE XXX}
// APP_XXX is the same as {$APPTYPE XXX}
// Defaults to console
// This is a work-around for a bug in FPC Cross Compiling to windows in delphi
// mode (IsConsole is always true)
{$DEFINE APP_GUI}

// This block actually does the work for the above work-around
{$IFDEF APP_GUI}
  {$APPTYPE GUI}
{$ELSE}
  {$IFDEF APP_FS}
    {$APPTYPE FS}
  {$ELSE}
    {$IFDEF APP_TOOL}
      {$DEFINE APP_CONSOLE}
      {$APPTYPE TOOL}
    {$ELSE}
      {$DEFINE APP_CONSOLE}
      {$APPTYPE CONSOLE}
    {$ENDIF}
  {$ENDIF}
{$ENDIF}

{ ----------------------------------------------------------------------------
  Debugging specific compiler directives
  ---------------------------------------------------------------------------- }

// If defined the JRE will always be downloaded as if it didn't exist. Used for
// testing the JRE download dialog.
// {$DEFINE FORCEJREDOWNLOAD}

uses
  kol, shared, Vista, Windows, SysUtils, classes, registry;

const
  // SetupConsts holds build information for this release
  {$I SetupConsts.inc}
  // This is also part of the above IsConsole workaround.
  {$IFDEF APP_CONSOLE}
    IsConsole: boolean = true;
  {$ELSE}
    IsConsole: boolean = false;
  {$ENDIF}

var
  { --------------------------------------------------------------------------
    KOL form objects
    -------------------------------------------------------------------------- }
  frmmain: pcontrol;
  progressbar, btncancel: pcontrol;
  label1, label2, label3, label4, labelurl, labelspeed, labelprogress: pcontrol;

  { --------------------------------------------------------------------------
    Other globals
    -------------------------------------------------------------------------- }
  terminateDownload: boolean = false;

procedure InitCommonControls; stdcall; External 'comctl32.dll' name 'InitCommonControls';

{ ----------------------------------------------------------------------------
  Main form: Cancel button clicked event
  ---------------------------------------------------------------------------- }
procedure btnCancel_Click(Dummy: Pointer; Sender: PControl);
begin
  terminateDownload := true;
end;

{ ----------------------------------------------------------------------------
  Main form: Set progress percentage to <value> and display in label <msg>
  ---------------------------------------------------------------------------- }
procedure setProgress(value: integer; msg: string);
begin
  ProgressBar.progress := value;
  labelprogress.Caption := msg;
  applet.processmessages;
end;

{ ----------------------------------------------------------------------------
  Initialise KOL and create the main window
  ---------------------------------------------------------------------------- }
procedure CreateMainWindow;
var
  screenw, screenh: longint;
  iconhandle: thandle;
begin
  { This call is required for common control 6 DLL to be correctly imported.
    Without it strange things happen on windows XP }
  InitCommonControls;

  { Load the icon to assign to our window }
  iconhandle := LoadIcon(hInstance, 'icon.ico');

  { We need the screen size to centre the window later }
  screenw := GetSystemMetrics(SM_CXSCREEN);
  screenh := GetSystemMetrics(SM_CYSCREEN);

  { KOL programs ideally need an Applet created }
  Applet := NewApplet('DMDirc Setup');
  Applet.Visible := true;

  Applet.Icon := iconhandle;

  { Create main form and set sane defaults. If we don't set the font here then
    all child objects will have a rubbish font as a holdover from Windows 3.1! }
  frmmain := NewForm(Applet, 'DMDirc Setup').SetClientSize(400, 184);
  frmmain.CreateVisible := True;
  frmmain.CanResize := False;
  frmmain.Style := frmmain.style and (not WS_MAXIMIZEBOX);
  frmmain.Font.FontName := 'Ms Sans Serif';
  frmmain.Font.FontHeight := 8;
  frmmain.SetPosition((screenw div 2) - (frmmain.Width div 2), (screenh div 2) - (frmmain.height div 2));

  frmmain.Icon := iconhandle;

  progressbar := NewProgressBar(frmmain).SetPosition(16, 114);
  progressbar.SetSize(frmmain.clientWidth - (progressbar.Left * 2), 16);
  progressbar.MaxProgress := 100;
  progressbar.Progress := 0;
  progressbar.Visible := true;

  btncancel := NewButton(frmmain, 'Cancel').SetPosition(progressbar.Left +
  progressbar.width - 60, progressbar.Top + progressbar.Height + 14);
  btncancel.SetSize(60, 24);

  label1 := NewLabel(frmmain, 'Downloading Java Runtime Environment').SetPosition(16, 16);
  label1.SetSize(frmmain.ClientWidth - 32, 16);
  label1.Font.FontStyle := [fsBold];

  label2 := NewLabel(frmmain, 'Address:').SetPosition(16, label1.top + 28);
  label2.SetSize(frmmain.ClientWidth - 32, 16);

  label3 := NewLabel(frmmain, 'Speed:').SetPosition(16, label2.top + 20);
  label3.SetSize(frmmain.ClientWidth - 32, 16);

  label4 := NewLabel(frmmain, 'Progress:').SetPosition(16, label3.top + 20);
  label4.SetSize(frmmain.ClientWidth - 32, 16);

  { BringToFront calls are needed on the following labels because the labels
    created earlier are as wide as the form and cover them as they are not
    transparent. It seems windows creates controls in a backwards order so newer
    controls are behind older ones. I could rearrange this order in the code but
    it would look messy. }

  labelurl := NewLabel(frmmain, '').SetPosition(70, label1.top + 28);
  labelurl.SetSize(frmmain.ClientWidth - 32, 16);
  labelurl.BringToFront;

  labelspeed := NewLabel(frmmain, '').SetPosition(70, label2.top + 20);
  labelspeed.SetSize(frmmain.ClientWidth - 32, 16);
  labelspeed.BringToFront;

  labelprogress := NewLabel(frmmain, '').SetPosition(70, label3.top + 20);
  labelprogress.SetSize(frmmain.ClientWidth - 32, 16);
  labelprogress.BringToFront;

  { Assign UI methods }
  btncancel.OnClick := TOnEvent(MakeMethod(nil, @btnCancel_Click ));

  { The window will not appear until the messageloop is started with Run() but
    this means we must yield this thread to the UI. This is unacceptable for
    such a simple program. Calling CreateWindow here will cause the window to
    appear but the message loop does not run; consequently the app must service
    messages by hand at a timely interval to avoid windows from marking the
    program as unresponsive. This is a hack but acceptable here. }

  { /!\ WARNING /!\ Run() can no longer be used to enter the message loop after
    this call or a nasty crash will occur. }
  applet.createwindow;
end;

{ ----------------------------------------------------------------------------
  Return the size in bytes of the file specified by <name>
  Returns -1 on error
  ---------------------------------------------------------------------------- }
function GetFileSizeByName(name: String): Integer;
var
  hand: THandle;
begin
  hand := 0;
  Result := 0;
  if FileExists(name) then begin
    try
      hand := CreateFile(PChar(name), GENERIC_READ, FILE_SHARE_WRITE or FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
      Result := GetFileSize(hand, nil);
    finally
      try
        if (hand <> 0) then CloseHandle(hand);
      except
        Result := -1;
      end;
    end;
  end;
end;

{$IFNDEF VER150}
{ ----------------------------------------------------------------------------
  Return part of a string
  ---------------------------------------------------------------------------- }
function AnsiMidStr(Source: String; Start: Integer; Count: Integer): String;
begin
  // Not perfectly accurate, but does the job
  { ^ What does that mean? // Zipplet }
  Result := Copy(Source, Start, Count);
end;
{$ENDIF}

{ ----------------------------------------------------------------------------
  Downloads the JRE. Returns TRUE if the user installed it. False otherwise
  ---------------------------------------------------------------------------- }
function downloadJRE(message: String = 'Would you like to download the java JRE?'): boolean;
var
  ProcessInfo: TProcessInformation;
  processResult: Longword;
  url: String;
  dir: String;
  line: String;
  f: TextFile;
  bits: TStringList;
  match: boolean;
  wantedsize: double;
  currentsize: double;
   lastsize: double;
   i: double;
   c: longint;
begin
  dir := IncludeTrailingPathDelimiter(ExtractFileDir(paramstr(0)));
  // TODO: We should probably download the 64bit version of java if we are
  // 64bit.
  url := 'http://www.dmdirc.com/getjava/windows/all';
  Result := false;

  { First we will determine the approximate size of the download.
    In my opinion we should not do this until we have asked the user if they
    would like to download the JRE. Might change this later.
    We obtain the size by asking wget to find out. }
  ExecAndWait('wget.exe -o "'+dir+'wgetoutput" --spider '+url, true);

  { Just incase wget fails ... }
  if not fileexists(dir+'wgetoutput') then begin
    showerror('Internal error: wget returned no output.', 'DMDirc Setup');
    result := false;
    exit;
  end;

  { Parse the output and grab the approximate size }
  AssignFile(f, dir+'wgetoutput');
  Reset(f);
  line := '';
  match := false;
  while not Eof(f) do begin
    ReadLn(f, line);
    if length(line) > 8 then begin
      if copy(line, 1, 7) = 'Length:' then begin
        match := true;
        break;
      end;
    end;
  end;
  if match then begin
    bits := TStringList.create;
    try
      bits.Clear;
      bits.Delimiter := ' ';
      bits.DelimitedText := line;
      try
        wantedsize := strtoint(StringReplace(bits[1], ',', '', [rfReplaceAll]))
      except
        wantedsize := 0;
      end;

      { We ask the user if they wish to download the JRE }
      if askQuestion(message+' (Download Size: '+AnsiMidStr(bits[2], 2, length(bits[2])-2)+')', 'DMDirc Setup') then begin
        { Create progress window and show it }
        CreateMainWindow;
        { Get wget to start the download }
        ProcessInfo := Launch('wget.exe '+url+' -O jre.exe', true);
        labelurl.caption := url;
        labelspeed.caption := 'Connecting to site...';

        { Why is this case needed ?! }
        if wantedsize <= 0 then begin
          progressbar.progress := 50;
        end;
        getExitCodeProcess(ProcessInfo.hProcess, processResult);

        lastsize := 0;
        c := 0;
        i := 0;
        while (processResult = STILL_ACTIVE) and (not terminateDownload) do begin
          if wantedsize > 0 then begin
            currentsize := GetFileSizeByName(dir + 'jre.exe');
            inc(c);
            if (c >= 5) then begin
              i := (i + currentsize - lastsize) / 2;
              labelspeed.caption := nicesize(round(i * 2)) + '/sec';
              lastsize := currentsize;
              c := 0;
            end;
            if (currentsize > 0) then setProgress(Round((currentsize/wantedsize)*100),
              nicesize(currentsize) + ' of ' + nicesize(wantedsize) +
              ' (' + inttostr(Round((currentsize/wantedsize)*100)) + '%)');
          end;
          { We must process the message loop or the window wont respond to the user }
          applet.ProcessMessages;
          { Sleep to prevent 100% CPU usage }
          sleep(100);
          GetExitCodeProcess(ProcessInfo.hProcess, processResult);
        end;
        frmmain.visible := false;
        applet.visible := false;
        if (terminateDownload) then begin
          Result := false;
          TerminateProcess(ProcessInfo.hProcess, 0);
          showError('JRE Download was aborted', 'DMDirc Setup', false);
        end
        else Result := processResult = 0;
        if not Result then begin
          if not terminateDownload then begin
            showError('JRE Download Failed', 'DMDirc Setup', false);
          end
          else begin
            // If the download was cancelled by the form, this error will already
            // have been given.
            { No action needed here anymore }
          end;
        end;
      end;
    finally
      bits.free;
    end;
  end;
end;

{ ----------------------------------------------------------------------------
  Begin the JRE download/install.
  ---------------------------------------------------------------------------- }
function installJRE(isUpgrade: boolean): boolean;
var
  question: String;
  needDownload: boolean;
  canContinue: boolean;
begin
  Result := false;
  needDownload := not FileExists(IncludeTrailingPathDelimiter(ExtractFileDir(paramstr(0)))+'jre.exe');
  if needDownload then begin
    if not isUpgrade then question := 'Java was not detected on your machine. Would you like to download and install it now?'
    else question := 'The version of java detected on your machine is not compatible with DMDirc. Would you like to download and install a compatible version now?';
  end
  else begin
    if not isUpgrade then question := 'Java was not detected on your machine. Would you like to install it now?'
    else question := 'The version of java detected on your machine is not compatible with DMDirc. Would you like to install a compatible version now?';
  end;

  canContinue := true;
  if (needDownload) then begin
    canContinue := downloadJRE(question);
  end;

  if canContinue then begin
    // Final result of this function is the return value of installing java.
    if needDownload or askQuestion(question, 'DMDirc Setup') then begin
      showmessage('The Java installer will now run. Please follow the instructions given. '+#13#10+'The DMDirc installation will continue afterwards.', 'DMDirc Setup');
      Result := (ExecAndWait('jre.exe') = 0);
    end;
  end
end;

{ ----------------------------------------------------------------------------
  MAIN PROGRAM
  ---------------------------------------------------------------------------- }
var
  errorMessage: String;
  params: String = '';
  dir: String = '';
  Reg: TRegistry;
  result: Integer;
begin

  errorMessage := '';
  if FileExists('DMDirc.jar') then begin
    {$IFDEF FORCEJREDOWNLOAD}if (1 <> 0) then begin{$ELSE}if (RunJava('-version') <> 0) then begin{$ENDIF}
      if not installJRE(false) then begin
        showError('DMDirc setup can not continue without Java. Please install Java and try again.', 'DMDirc Setup', false, false);
        exit;
      end;
    end;

    Reg := TRegistry.Create;
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\DMDirc', false) then begin
      dir := Reg.ReadString('InstallDir');
      if (dir <> '') then begin
        params := params+' --directory "'+dir+'"';
      end;
    end;
    Reg.CloseKey;
    Reg.Free;
    if (ReleaseNumber <> '') then begin
      params := params+' --release '+ReleaseNumber;
    end;
    // Check if the installer runs
    if (RunJava('-cp DMDirc.jar com.dmdirc.installer.Main --help') <> 0) then begin
      if not installJRE(true) then begin
        showError('Sorry, DMDirc setup can not continue without an updated version of java.', 'DMDirc Setup', false, false);
        exit;
      end
      else begin
        // Try again now that java is installed.
        result := RunJava('-cp DMDirc.jar com.dmdirc.installer.Main '+params);
      end;
    end
    else begin
      // Java is the right version, run the installer
      result := RunJava('-cp DMDirc.jar com.dmdirc.installer.Main '+params);
    end;
  end
  else begin
    errorMessage := errorMessage+'DMDirc.jar was not found.';
    errorMessage := errorMessage+#13#10;
    errorMessage := errorMessage+#13#10+'This is likely because of a corrupt installer build.';
    errorMessage := errorMessage+#13#10+'Please check http://www.dmdirc.com/ for an updated build.';
    showError(errorMessage, 'DMDirc Setup');
  end;
end.
