; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "Studio"
#define MyAppVersion "1.0"
#define MyAppPublisher "UVC"
#define MyAppExeName "ProgramLauncher.exe"
#define MyAppAssocName MyAppName + " File"
#define MyAppAssocExt ".myp"
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{8268FFE4-0A54-4812-8500-B2660BC5956D}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={localappdata}\UVC\{#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}
ChangesAssociations=yes
DisableProgramGroupPage=yes
DisableDirPage=no
; Uncomment the following line to run in non administrative install mode (install for current user only).
;PrivilegesRequired=lowest
OutputDir=C:\Users\SJM\Desktop
OutputBaseFilename=StudioSetup
SetupIconFile=C:\Users\SJM\Desktop\업데이트 기능\TestIcon.ico
SolidCompression=yes
WizardStyle=modern

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "C:\Users\SJM\Desktop\업데이트 기능\Launcher_Studio\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\Users\SJM\Desktop\업데이트 기능\Launcher_Studio\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Registry]
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
Root: HKCU; Subkey: "Software\UVC\{#MyAppName}"; \
    ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: uninsdeletekey

[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

[UninstallDelete]
Type: filesandordirs; Name: "{app}"

[Code]
const
  WM_COPYDATA = $004A;

type
  TProcessInfoSet = record
    PathSet: TStringList;
    NameSet: TStringList;
  end;

// -------------------- StudioInstaller 메시지 전송 --------------------
function RegisterWindowMessage(lpString: AnsiString): LongWord;
  external 'RegisterWindowMessageA@user32.dll stdcall';

procedure NotifyStudioInstaller(const msg: AnsiString);
var
  msgID: LongWord;
begin
  msgID := RegisterWindowMessage(msg);
  if msgID <> 0 then
  begin
    PostMessage($FFFF, msgID, 0, 0);
  end;
end;


// -------------------- PowerShell 실행 및 결과 추출 --------------------
function ExecAndGetLines(const CommandLine: string; var Lines: TArrayOfString): Boolean;
var
  TempFile: string;
  Success: Boolean;
  ResultCode: Integer;
begin
  TempFile := ExpandConstant('{tmp}\ps_output.txt');

  Success := Exec(
    'powershell.exe',
    '-Command "$ErrorActionPreference = ''SilentlyContinue''; Get-Process | ForEach-Object { $p = $_; $path = ''''; try { $path = $p.Path } catch {}; ''{0}|{1}'' -f $path, $p.Name } | Out-File -Encoding UTF8 ''' + TempFile + '''"',
    '',
    SW_HIDE,
    ewWaitUntilTerminated,
    ResultCode);

  if Success then
    Result := LoadStringsFromFile(TempFile, Lines)
  else
    Result := False;
end;

// -------------------- 유틸리티 --------------------
function PosEx(const SubStr, S: string; Offset: Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := Offset to Length(S) - Length(SubStr) + 1 do
  begin
    if Copy(S, i, Length(SubStr)) = SubStr then
    begin
      Result := i;
      Exit;
    end;
  end;
end;

function SplitString(const S, Delimiter: string): TArrayOfString;
var
  PosStart, PosDelim, Index: Integer;
begin
  SetArrayLength(Result, 0);
  PosStart := 1;
  Index := 0;
  repeat
    PosDelim := PosEx(Delimiter, S, PosStart);
    if PosDelim > 0 then
    begin
      SetArrayLength(Result, Index + 1);
      Result[Index] := Copy(S, PosStart, PosDelim - PosStart);
      PosStart := PosDelim + Length(Delimiter);
      Inc(Index);
    end
    else
    begin
      SetArrayLength(Result, Index + 1);
      Result[Index] := Copy(S, PosStart, Length(S));
      Break;
    end;
  until False;
end;

// -------------------- 실행 중 프로세스 집합 구성 --------------------
function GetRunningProcessSets: TProcessInfoSet;
var
  Lines: TArrayOfString;
  I: Integer;
  Parts: TArrayOfString;
  Path, Name: string;
begin
  Result.PathSet := TStringList.Create;
  Result.PathSet.CaseSensitive := False;
  Result.PathSet.Sorted := True;
  Result.PathSet.Duplicates := dupIgnore;

  Result.NameSet := TStringList.Create;
  Result.NameSet.CaseSensitive := False;
  Result.NameSet.Sorted := True;
  Result.NameSet.Duplicates := dupIgnore;

  if not ExecAndGetLines('', Lines) then
    Exit;

  for I := 0 to GetArrayLength(Lines) - 1 do
  begin
    Parts := SplitString(Lines[I], '|');
    if GetArrayLength(Parts) = 2 then
    begin
      Path := LowerCase(Trim(Parts[0]));
      Name := LowerCase(Trim(Parts[1]) + '.exe');

      if Path <> '' then
        Result.PathSet.Add(Path)
      else
        Result.NameSet.Add(Name);
    end;
  end;
end;

procedure FreeProcessSet(var ProcSet: TProcessInfoSet);
begin
  ProcSet.PathSet.Free;
  ProcSet.NameSet.Free;
end;

// -------------------- 실행 중 여부 판단 --------------------
function IsFileRunningOptimized(const TargetFile: string; const ProcSet: TProcessInfoSet): Boolean;
var
  LowerPath: string;
  FileName: string;
begin
  LowerPath := LowerCase(TargetFile);
  FileName := LowerCase(ExtractFileName(TargetFile));
  Result := (ProcSet.PathSet.IndexOf(LowerPath) <> -1) or
            (ProcSet.NameSet.IndexOf(FileName) <> -1);
end;

function IsExcluded(const FileName: string): Boolean;
begin
  Result := (LowerCase(FileName) = 'unins000.exe');
end;

// -------------------- 디렉터리 재귀 탐색 --------------------
function HasRunningFilesInDir(const DirPath: string; const ProcSet: TProcessInfoSet): Boolean;
var
  FindRec: TFindRec;
  FullPath, Extension: string;
begin
  Result := False;

  if FindFirst(DirPath + '\*', FindRec) then
  begin
    repeat
      if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
      begin
        if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
        begin
          if HasRunningFilesInDir(DirPath + '\' + FindRec.Name, ProcSet) then
          begin
            Result := True;
            Break;
          end;
        end;
      end
      else
      begin
        FullPath := DirPath + '\' + FindRec.Name;
        Extension := LowerCase(ExtractFileExt(FindRec.Name));

        if (Extension = '.exe') and not IsExcluded(FindRec.Name) then
        begin
          if IsFileRunningOptimized(FullPath, ProcSet) then
          begin
            Result := True;
            Break;
          end;
        end;
      end;
    until not FindNext(FindRec);
    FindClose(FindRec);
  end;
end;

// -------------------- Entry Point --------------------
function InitializeUninstall(): Boolean;
var
  ProcSet: TProcessInfoSet;
begin
  ProcSet := GetRunningProcessSets;

  if HasRunningFilesInDir(ExpandConstant('{app}'), ProcSet) then
  begin
    MsgBox('프로그램 실행 중인 파일이 감지되었습니다. 모든 관련 프로그램을 종료한 후 다시 시도하세요.', mbError, MB_OK);
    Result := False;
  end
  else
    Result := True;

  FreeProcessSet(ProcSet);
end;

// -------------------- 인스톨/언인스톨 완료 시 StudioInstaller에 알림 --------------------
procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssPostInstall then
    NotifyStudioInstaller('INSTALL_COMPLETE');
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  if CurUninstallStep = usPostUninstall then
    NotifyStudioInstaller('UNINSTALL_COMPLETE');
end;