package main

import (
	"encoding/json"
	"errors"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"time"
)

func copy(src, dst string, stdlog *log.Logger) error {
	fi, err := os.Stat(src)
	if err != nil {
		return err
	}
	if fi.IsDir() {
		entries, _ := os.ReadDir(src)
		for _, ent := range entries {
			if err := copy(path.Join(src, ent.Name()), path.Join(dst, ent.Name()), stdlog); err != nil {
				return err
			}
		}
		return nil
	}

	inmode := fi.Mode()

	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	copied, err := io.Copy(out, in)
	if err != nil {
		return err
	}
	if copied < fi.Size() {
		return errors.New("copy not completed")
	}
	if err := out.Sync(); err != nil {
		return err
	}

	if err := out.Chmod(inmode); err != nil {
		return err
	}

	stdlog.Println("file copied :", src, dst)
	return nil
}

func main() {
	logfile, _ := os.OpenFile("replacer.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
	defer logfile.Close()
	stdlog := log.New(logfile, "", log.LstdFlags)

	args := os.Args
	// args[1] : 나를 시작한 pid. pid가 종료될 때 까지 기다림
	// args[2] : target 폴더
	// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
	stdlog.Println(args)

	for {
		stdlog.Println("wait for terminating of", args[3])
		cmd := exec.Command("ps", "-p", args[1])
		if err := cmd.Run(); err != nil {
			break
		}
		time.Sleep(time.Second)
	}

	stdlog.Println("target is terminated")

	// replacer 제거. 내가 돌고 있으므로 복사는 안된다.
	// 내가 실행되기 전에 이미 복사가 되서 나는 최신 버전임
	os.Remove(path.Join(args[2], os.Args[0]))
	if err := copy(args[2], "", stdlog); err != nil {
		stdlog.Fatal(err)
	}

	nextArgs := args[4:]
	if bt, _ := os.ReadFile("@args"); len(bt) > 0 {
		var tempArgs []string
		if json.Unmarshal(bt, &tempArgs) == nil {
			nextArgs = tempArgs
		}
	}
	os.Remove("@args")

	err := os.RemoveAll(args[2])
	if err != nil {
		stdlog.Println("os.RemoveAll failed :", args[2], err)
	}

	err = os.Chmod(args[3], 0775)
	if err != nil {
		stdlog.Println("os.Chmod failed :", err)
	}

	stdlog.Println("exec.Command :", args)
	cmd := exec.Command(args[3], nextArgs...)
	cmd.Start()
}
