
const
  ATTACH_HOOK = 0;
  DETACH_HOOK = 1;
  CHECK_FOR_UPDATE = 2;
  BEFORE_UPDATE = 3;
  SUCCESS_UPDATE = 4;
  AFTER_UPDATE = 5;

type
  THookFunc = function(Param1: string; Param2: integer): boolean;
  THookArr = array[ATTACH_HOOK..AFTER_UPDATE] of THookFunc;
  TUpdater = record
    Name, URL, VersionURL, Folder: string;
    CreateMenu, NoMenuTimer, Locked: boolean;
    Hooks: THookArr;
    ViewMenuItem: TMenuItem;
    MainMenu, UpdateMenuItem, CheckMenuItem: TMenuItem;
    AutoMenu, AUpdateMenuItem, ACheckMenuItem: TMenuItem;
    SettingsMenu, TimerMenuItem, OverrideUpdateMenuItem: TMenuItem;
    Timer: TTimer;
  end;
  TUpdaterArr = array of TUpdater;

var
  UpdaterArr: TUpdaterArr;

function GetOVersion(Updater: TUpdater): integer;
begin
  Result := StrToIntDef(ExtractFromStr(GetPage(Updater.VersionURL), Numbers), -1);
end;

function GetLVersion(Updater: TUpdater): integer;
begin
  Result := StrToIntDef(ExtractFromStr(Settings.GetKeyValueDef(Updater.Name + '_Version', '-1'), Numbers), -1);
end;

procedure SetLVersion(Updater: TUpdater; Version: integer);
begin
  Settings.SetKeyValue(Updater.Name + '_Version', IntToStr(Version));
end;

procedure Update(const Sender: TObject; const I: integer);
var
  Cont, ExCont: string;
begin
  with UpdaterArr[I] do
  begin
    if (Locked) then
      Exit;

    if (Hooks[BEFORE_UPDATE] <> nil) then
      if (not (Hooks[BEFORE_UPDATE]('', I))) then
        Exit;

    Locked := True;

    WriteLn(Name + ' Updater: Local Version - "' + IntToStr(GetLVersion(UpdaterArr[I])) + '"  |  Remote Version = "' + IntToStr(GetOVersion(UpdaterArr[I])) + '"');
    WriteLn(Name + ' Updater: Update File - "' + URL + '"');
    WriteLn(Name + ' Updater: Downloading...');
    Cont := GetPage(URL);
    if (Cont <> '') then
    begin
      WriteLn(Name + ' Updater: Decompressing...');
      if (DecompressBZip2(Cont, ExCont, 4096)) then
      begin
        WriteLn(Name + ' Updater: Extracting...');
        if (UnTarEx(ExCont, Folder, True)) then
        begin
          if (Hooks[SUCCESS_UPDATE] <> nil) then
            if (not (Hooks[SUCCESS_UPDATE](ExCont, I))) then
            begin
              WriteLn(Name + ' Update ERROR: Kill recieved from Updater.');
              Exit;
            end;
          SetLVersion(UpdaterArr[I], GetOVersion(UpdaterArr[I]));
          WriteLn(Name + ' Update Successful!');
          if (CreateMenu) then
            UpdateMenuItem.Enabled := OverrideUpdateMenuItem.Checked;
        end else
          WriteLn(Name + ' Update ERROR: Couldn''t extract the tar archive.');
      end else
        WriteLn(Name + ' Update ERROR: Couldn''t decompress the bzip archive.');
    end else
      WriteLn(Name + ' Update ERROR: Didn''t recieve any data from Update URL.');

    Locked := False;
    if (Hooks[AFTER_UPDATE] <> nil) then
      if (not (Hooks[AFTER_UPDATE]('', I))) then
        Exit;
  end;
end;

procedure Tick(const Sender: TObject);
var
  I: integer;
  Name: string;
begin
  for I := 0 to High(UpdaterArr) + 1 do
    if (not (I = High(UpdaterArr) + 1)) then
    begin
      case Sender of
        UpdaterArr[I].Timer: Name := 'Timer';
        UpdaterArr[I].CheckMenuItem: Name := 'Check';
      end;
      if (not (Name = '')) then
        Break;
    end else
      Exit;

  if (UpdaterArr[I].Locked) then
    Exit;

  if (UpdaterArr[I].Hooks[CHECK_FOR_UPDATE] <> nil) then
    if (not (UpdaterArr[I].Hooks[CHECK_FOR_UPDATE](Name, I))) then
      Exit;

  if (GetLVersion(UpdaterArr[I]) < GetOVersion(UpdaterArr[I])) then
  begin
    WriteLn('You have a new update available for ' + UpdaterArr[I].Name + '!');

    if (UpdaterArr[I].CreateMenu) then
    begin
      UpdaterArr[I].UpdateMenuItem.Enabled := True;
      if (UpdaterArr[I].AUpdateMenuItem.Checked or (Sender = UpdaterArr[I].CheckMenuItem)) then
        Update(Sender, I);
    end else
      Update(Sender, I);
  end else
    if (Sender = UpdaterArr[I].CheckMenuItem) then
      WriteLn('No ' + UpdaterArr[I].Name + ' update available.');
end;

procedure Click(Sender: TObject);
var
  I: integer;
  Value: string;
begin
  for I := 0 to High(UpdaterArr) do
    with UpdaterArr[I] do
    begin
      case Sender of
        ViewMenuItem: begin
            ViewMenuItem.Checked := (not (ViewMenuItem.Checked));
            Settings.SetKeyValue(Name + '_Visible', Lowercase(BoolToStr(ViewMenuItem.Checked)));
            MainMenu.Visible := ViewMenuItem.Checked;
          end;
        UpdateMenuItem: Update(Sender, I);
        CheckMenuItem: Tick(Sender);
        AUpdateMenuItem, ACheckMenuItem, OverrideUpdateMenuItem: begin
            TMenuItem(Sender).Checked := (not (TMenuItem(Sender).Checked));
            Settings.SetKeyValue(UpdaterArr[I].Name + '_' + ExtractFromStr(TMenuItem(Sender).Caption, Letters), BoolToStr(TMenuItem(Sender).Checked));
            case Sender of
              OverrideUpdateMenuItem: UpdateMenuItem.Enabled := OverrideUpdateMenuItem.Checked;
              ACheckMenuItem: Timer.Enabled := ACheckMenuItem.Checked;
            end;
          end;
        TimerMenuItem: begin
            if (InputQuery(Name + ' Updater', 'What would you like the Timer Interval to be? (1 - 100000 seconds)', Value)) then
            begin
              Value := ExtractFromStr(Value, Numbers);
              if ((IntToStr(StrToIntDef(Value, -1)) = Value) and InRange(StrToIntDef(Value, -1), 1, 100000)) then
              begin
                Timer.Interval := StrToIntDef(Value, (Timer.Interval / 1000)) * 1000;
                TimerMenuItem.Caption := 'Timer Interval: ' + IntToStr(Timer.Interval / 1000);
                Settings.SetKeyValue(Name + '_TimerInterval', IntToStr(Timer.Interval));
              end;
            end;
          end;
      end;
    end;
end;

function InMenu(const Name: string; const Menu: TMenuItem; out Item: TMenuItem): boolean;
var
  I: integer;
begin
  for I := 0 to Menu.Count - 1 do
    if (Lowercase(Menu.Items[I].Caption) = Lowercase(Name)) then
    begin
      Result := True;
      Item := Menu.Items[I];
      Exit;
    end;
end;

function GetMenuByName(const Name: string; Menu: TMenuItem): TMenuItem;
begin
  if (not (InMenu(Name, Menu, Result))) then
  begin
    Result := TMenuItem.Create(Menu);
    Result.Caption := Name;
    Menu.Add(Result);
  end;
end;

function MoveMenuItem(Menu: TMenuItem; FromIndex, ToIndex: integer): TMenuItem;
var
  TempMenuItem: TMenuItem;
begin
  if (InRange(FromIndex, 0, Menu.Count - 1) and InRange(ToIndex, 0, Menu.Count - 1)) then
  begin
    TempMenuItem := Menu.Items[FromIndex];
    Menu.Delete(FromIndex);
    Menu.Insert(ToIndex, TempMenuItem);
    Result := Menu.Items[ToIndex];
  end;
end;

function GetIndex(Menu, MenuItem: TMenuItem): integer;
begin
  for Result := 0 to Menu.Count - 1 do
    if (Menu.Items[Result] = MenuItem) then
      Exit;
  Result := -1;
end;

function IsUpdateMenu(const Menu: TMenuItem): boolean;
var
  I: integer;
begin
  for I := 0 to Menu.Count - 1 do
    if (Menu.Items[I].Caption = 'Update') then
      Result := True;
end;

function AddUpdater(const IncludeName, UpdateURL, VerURL, ExFolder: string; const ShowMenu, TimerIfNoMenu: boolean; var ID: integer): boolean;
var
  Seperator: array[0..1] of TMenuItem;
  ViewMenu: TMenuItem;
begin
  ID := Length(UpdaterArr);
  SetLength(UpdaterArr, ID + 1);
  with UpdaterArr[ID] do
  begin
    Name := IncludeName;
    URL := UpdateURL;
    VersionURL := VerURL;
    Folder := ExFolder;
    CreateMenu := ShowMenu;
    NoMenuTimer := TimerIfNoMenu;

    if (CreateMenu) then
    begin
      ViewMenu := GetMenuByName('&View', Simba_MainMenu.Items);
      if (ViewMenu.Count = 4) then
      begin
        Seperator[0] := TMenuItem.Create(ViewMenu);
        Seperator[0].Caption := '-';
        ViewMenu.Add(Seperator[0])
      end;

      ViewMenuItem := GetMenuByName(Name + ' Menu', ViewMenu);
      ViewMenuItem.OnClick := @Click;
      ViewMenuItem.Checked := (LowerCase(Settings.GetKeyValueDef(Name + '_Visible', 'true')) = 'true');
      ViewMenuItem.Visible := False;

      MainMenu := GetMenuByName(Name, Simba_MainMenu.Items);
      MainMenu.Visible := False;
      if (IsUpdateMenu(MainMenu)) then
        Exit;

      if (MainMenu.Count > 0) then
      begin
        Seperator[1] := TMenuItem.Create(MainMenu);
        Seperator[1].Caption := '-';
        MainMenu.Add(Seperator[1])
      end;

      UpdateMenuItem := TMenuItem.Create(MainMenu);
      UpdateMenuItem.Caption := 'Update';
      UpdateMenuItem.OnClick := @Click;
      UpdateMenuItem.Enabled := (LowerCase(Settings.GetKeyValueDef(Name + '_OverrideUpdate', 'false')) = 'true');
      MainMenu.Add(UpdateMenuItem);

      CheckMenuItem := TMenuItem.Create(MainMenu);
      CheckMenuItem.Caption := 'Check for Update';
      CheckMenuItem.OnClick := @Click;
      MainMenu.Add(CheckMenuItem);

      AutoMenu := TMenuItem.Create(MainMenu);
      AutoMenu.Caption := 'Automatically ...';
      MainMenu.Add(AutoMenu);

      AUpdateMenuItem := TMenuItem.Create(AutoMenu);
      AUpdateMenuItem.Caption := 'Update';
      AUpdateMenuItem.OnClick := @Click;
      AUpdateMenuItem.Checked := (LowerCase(Settings.GetKeyValueDef(Name + '_Update', 'true')) = 'true');
      AutoMenu.Add(AUpdateMenuItem);

      ACheckMenuItem := TMenuItem.Create(AutoMenu);
      ACheckMenuItem.Caption := 'Check for Updates';
      ACheckMenuItem.OnClick := @Click;
      ACheckMenuItem.Checked := (LowerCase(Settings.GetKeyValueDef(Name + '_CheckforUpdates', 'true')) = 'true');
      AutoMenu.Add(ACheckMenuItem);

      SettingsMenu := TMenuItem.Create(MainMenu);
      SettingsMenu.Caption := 'Settings';
      MainMenu.Add(SettingsMenu);

      TimerMenuItem := TMenuItem.Create(SettingsMenu);
      TimerMenuItem.Caption := 'Timer Interval: ' + IntToStr(StrToIntDef(ExtractFromStr(Settings.GetKeyValueDef(Name + '_TimerInterval', '3600000'), Numbers), 3600000) / 1000);
      TimerMenuItem.OnClick := @Click;
      SettingsMenu.Add(TimerMenuItem);

      OverrideUpdateMenuItem := TMenuItem.Create(SettingsMenu);
      OverrideUpdateMenuItem.Caption := 'Override Update';
      OverrideUpdateMenuItem.OnClick := @Click;
      OverrideUpdateMenuItem.Checked := (LowerCase(Settings.GetKeyValueDef(Name + '_OverrideUpdate', 'false')) = 'true');
      SettingsMenu.Add(OverrideUpdateMenuItem);
    end;

    Timer := TTimer.Create(nil);
    Timer.Interval := StrToIntDef(ExtractFromStr(Settings.GetKeyValueDef(Name + '_TimerInterval', '3600000'), Numbers), 3600000);
    if (ShowMenu) then
      Timer.Enabled := ACheckMenuItem.Checked
    else
      Timer.Enabled := NoMenuTimer;
    Timer.OnTimer := @Tick;
  end;

  Result := True;
end;

{ procedure Free; //Doesn't Work
var
  I: integer;
begin
  for I := 0 to High(UpdaterArr) do
    with UpdaterArr[I] do
      if (Timer <> nil) then
        Timer.Free;
end; }

procedure Attach;
var
  I: integer;
begin
  for I := 0 to High(UpdaterArr) do
  begin
    if (UpdaterArr[I].Hooks[ATTACH_HOOK] <> nil) then
      UpdaterArr[I].Hooks[ATTACH_HOOK]('', I);

    if (UpdaterArr[I].CreateMenu) then
    begin
      UpdaterArr[I].ViewMenuItem.Visible := True;
      UpdaterArr[I].MainMenu.Visible := UpdaterArr[I].ViewMenuItem.Checked;
      UpdaterArr[I].Timer.Enabled := UpdaterArr[I].ACheckMenuItem.Checked;
    end else
      UpdaterArr[I].Timer.Enabled := UpdaterArr[I].NoMenuTimer;
    WriteLn(UpdaterArr[I].Name + ' Updater Enabled!');
  end;
end;

procedure Detach;
var
  I: integer;
begin
  for I := High(UpdaterArr) downto 0 do
  begin
    if (UpdaterArr[I].Hooks[DETACH_HOOK] <> nil) then
      UpdaterArr[I].Hooks[DETACH_HOOK]('', I);

    if (UpdaterArr[I].CreateMenu) then
    begin
      UpdaterArr[I].ViewMenuItem.Visible := False;
      UpdaterArr[I].MainMenu.Visible := False;
    end;
    UpdaterArr[I].Timer.Enabled := False;
    WriteLn(UpdaterArr[I].Name + ' Updater Disabled.');
  end;
end;

