#!/usr/bin/php
<?php

require __DIR__.'/lib/bootstrap.php';

class CProjectGitExporter extends CProject {

    function __construct(string $projdirname, string $projname) {
        CProject::__construct($projdirname, $projname);

        $this->allowText2Html = false; // Don't produce HTML when processing the download block
    }

    function ExportAsGit(string $dest) {

        // Create destination directory
        if (is_dir($dest)) {
            throw new Exception("Destination path '{$dest}' already exists");
        }
        mkdir($dest);
        shell_exec('cd '. escapeshellarg($dest).' && git init');
            
        // Parse download information blocks
        $this->filterLongDescArea();

        // Prepare to start modifying the generated README.md file
        $this->longdesc = preg_replace('~\\[url=([^\\]]+?)\\](.+?)\\[/url\\]~m', '[\\2](\\1)', $this->longdesc);
        $this->longdesc = str_replace("\xEF\xBB\xBF", "", $this->longdesc); // Remove interior UTF-8 BOM markers
        $this->longdesc = str_replace("\r", "", $this->longdesc); // dos2unix

        // Add main header and "written in" badge
        $lines = explode("\n", $this->longdesc);
        $header = ["# ".$this->projname, ""];
        if (strlen($this->subtag) > 0) {
            $header[] = "![](https://img.shields.io/badge/written%20in-" . rawurlencode($this->subtag) . "-blue)";
            $header[] = "";
        }
        $lines = array_merge($header, $lines);
        
        // Modify some lines to standard markdown format:
        foreach($lines as $i => $line) {

            // Convert ==HEADERS== to ## Headers
            if (strlen($line) > 0 && $line[0] == '=') {
                $tmp = rtrim($line, '=');
                $indent = substr_count($tmp, '=');

                // Normalise the capitalisation for header text
                $header_text = substr($tmp, $indent);
                if ($header_text != "TODO" && $header_text != "FIXME" && $header_text != "WARNING" && $header_text != "API") {
                    $header_text = ucwords(strtolower($header_text));
                }

                // raise markdown-equivalent header level by 1 to compensate for new header
                $lines[$i] = $line = str_repeat('#', $indent + 1).' '.$header_text;
            }

            // Convert multiline single-backtick to multi-backtick
            // Heuristic: if there is only a single backtick in the line
            if (substr_count($line, '`') == 1) {
                $lines[$i] = $line = str_replace('`', "\n```\n", $line);
            }

            // If the previous line started with a hyphen, and this line didn't, we need an 
            // extra line separator
            if ($i > 0 && strlen($line) > 0 &&
                $line[0] != '-' && strlen($lines[$i-1]) > 0 && $lines[$i-1][0] == '-'
            ) {
                $lines[$i] = $line = "\n".$line;
            }
        }
        
        // Copy all images to a doc/ subdirectory
        if (count($this->images) > 0) {
            mkdir($dest.'/doc');
            foreach($this->images as $img) {
                // Normalise some legacy file extension variants while we're here
                copy($this->dir.$img, $dest.'/doc/'.str_replace(['.jpeg', '.JPG', '.JPEG'], ['.jpg', '.jpg', '.jpg'], $img));
            }
        }

        // Copy all downloads to a dist-archive/ subdirectory
        if (count($this->downloads) > 0) {

            mkdir($dest.'/dist-archive');
            foreach($this->downloads as $file) {
                copy($this->dir.$file, $dest.'/dist-archive/'.$file);
            }

            if (! $this->downloads_section_was_replaced) {
                // Add our own downloads section
                $lines[] = $this->renderDownloadsBlock($this->downloads, true);
            }
        }

        // Save final README
        file_put_contents($dest.'/README.md', implode("\n", $lines));

        // Extra properties file
        $ctime = $this->lastupdate - ($this->lifespan * 3600); // Lifespans are measured in hours

        if (strlen($this->shortdesc) > 0) {
            $this->shortdesc = rtrim($this->shortdesc, '.').'.';
            $this->shortdesc[0] = strtoupper($this->shortdesc[0]);
        }

        file_put_contents($dest.'/.legacy-codesite.toml', "# Converted with codesite2git

project_name=".json_encode($this->projname)."
short_description=".json_encode($this->shortdesc)."
written_in_lang=".json_encode($this->subtag)."
topics=".json_encode($this->tags)."
ctime=".$ctime."
mtime=".$this->lastupdate."

");

        // Git commit everything
        // Once for the meta with ctime; once for all files with the mtime
        $command = (
            'cd '. escapeshellarg($dest).' && '.
            'git add .legacy-codesite.toml ; '.
            'GIT_COMMITTER_DATE="'.date(DATE_ISO8601, $ctime).'" git commit -m "initial meta commit" --date '.escapeshellarg(date(DATE_ISO8601, $ctime)).' ; '.
            'git add -A ; '.
            'GIT_COMMITTER_DATE="'.date(DATE_ISO8601, $this->lastupdate).'" git commit -m "commit all archived files" --date '.escapeshellarg(date(DATE_ISO8601, $this->lastupdate))
        );
        //echo $command."\n";
        shell_exec($command);

    }

    public function renderDownloadsBlock($render_downloads, $include_header=false) { // override
		if (! count($render_downloads)) {
			return;
		}
		
		$ret = "\n";
        if ($include_header) {
            $ret .= "## Download\n\n";
        }
        foreach($render_downloads as $filename) {
            // It's possible to make a general URL that links directly to the raw download endpoint
            // But because our markdown is rendered in context of the repo, we can't properly make a relative link
            // We could make an absolute link, but that's probably a bad idea w.r.t. any future Git migrations
            // Just link to the file in-repo although it won't be a RAW download link

            $ret .= "- [⬇️ {$filename}](dist-archive/".rawurlencode($filename).") "; // raw/branch/master/dist-archive/{$filename}
            $ret .= "*(".fbytes(filesize($this->dir.$filename)).")*\n";
        }
        
		return $ret;
	}

}

class Gitea {
    protected $url;
    protected $org;
    protected $token;

    public function __construct(string $url, string $org, string $token) {
        $this->url = $url;
        $this->org = $org;
        $this->token = $token;
    }

    public function repoExists(string $repo): bool {

        $resp = @file_get_contents($this->url.'api/v1/repos/'.$this->org.'/'.$repo);
        if ($resp === false) {
            return false; // not conclusive but it will suffice
        }
        if (strpos($resp, 'does not exist')) {
            return false;
        }

        $inf = json_decode($resp, true);
        if (is_array($inf) && $inf['name'] == $repo) {
            return true; // definitely yes
        }

        throw new \Exception("Couldn't decide whether repo {$repo} exists\n\n".var_export($resp, true));
    }

    public function createRepo(string $repo) {
        $command = ('curl -sS -X POST ' .
            escapeshellarg($this->url.'api/v1/admin/users/'.$this->org.'/repos?access_token='.urlencode($this->token)) . ' '.
            '-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode(['name' => $repo]))
        );

        $code = 0;
        $output = [];
        exec($command, $output, $code);
        if ($code == 0) {
            return; // success
        }

        throw new \Exception("failed to create repository {$repo} (exit code {$code})\n\n".var_export($output, true));
    }

    public function updateRepoProperties(string $repo, string $description) {
        $json_update = [
            'archived' => true,
            'description' => $description,

            // SPECIAL https://code.ivysaur.me/ - don't leave in commit {{{
            'has_wiki' => false,
            'website' => 'https://code.ivysaur.me/'.$repo,
            // }}}
        ];

        $command = ('curl -sS -X PATCH ' .
            escapeshellarg($this->url.'api/v1/repos/'.$this->org.'/'.$repo.'?access_token='.urlencode($this->token)) . ' '.
            '-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode($json_update))
        );

        $code = 0;
        $output = [];
        exec($command, $output, $code);
        if ($code == 0) {
            return; // success
        }

        throw new \Exception("failed to update repository {$repo} properties: (exit code {$code})\n\n".var_export($output, true));
    }

    public function setRepoTopics(string $repo, array $topics) {
        $command = ('curl -sS -X PUT ' .
            escapeshellarg($this->url.'api/v1/repos/'.$this->org.'/'.$repo.'/topics?access_token='.urlencode($this->token)) . ' '.
            '-H "accept: application/json" -H "Content-Type: application/json" -d ' . escapeshellarg(json_encode(['topics' => $topics]))
        );

        $code = 0;
        $output = [];
        exec($command, $output, $code);
        if ($code == 0) {
            return; // success
        }

        throw new \Exception("failed to update repository {$repo} topics: (exit code {$code})\n\n".var_export($output, true));
    }

    public function gitRemoteUrlFor(string $repo): string {
        return $this->url.$this->org.'/'.$repo.'.git';
    }
}

function codesite2git(string $projdirname, string $projname, string $dest): CProjectGitExporter {

    // Parse existing project
    $c = new CProjectGitExporter($projdirname, $projname);
    // var_dump($c);
    $c->ExportAsGit($dest);

    return $c;
}

function usage() {
    die("Usage:
    codesite2git [FLAGS...]

Options:
    --single CODESITE_ROOT_PATH PROJECT_DIR_NAME PROJECT_REAL_NAME DEST_DIR
    --all    CODESITE_ROOT_PATH TEMP_DIR GITEA_URL GITEA_ORG GITEA_AUTH_TOKEN
");
    die(1);
}

function main(array $argv) {
    if (count($argv) < 2) {
        usage();
    }

    if ($argv[1] == '--single') {
        if (count($argv) != 6) {
            usage();
        }

        $config = setup_vars($argv[2]);
        codesite2git($argv[3], $argv[4], $argv[5]);

    } else if ($argv[1] == "--all") {
        if (count($argv) != 7) {
            usage();
        }

        $config = setup_vars($argv[2]);
        $repos = glob(BASEDIR.'data/*');
        
        $temp_dir = $argv[3];
        $gitea = new Gitea($argv[4], $argv[5], $argv[6]);

        foreach($repos as $i => $path) {

            $projdir = basename($path);
            $reponame = explode('-', $projdir, 2)[1];

            echo sprintf("[%3d/%3d] Processing '%s'... ", $i+1, count($repos), $reponame);

            // Convert to git
            $git_repo_dir = $temp_dir.'/'.$projdir.'-archive.git';
            $c = codesite2git($projdir, $reponame, $git_repo_dir);

            // Check if Gitea repo already exists
            if ($gitea->repoExists($reponame)) {
                throw new \Exception("Abandoning process since repo {$reponame} already exists in Gitea - move it out of the source directory first");
            }

            // Create new Gitea repo
            $gitea->createRepo($reponame);
            if (! $gitea->repoExists($reponame)) {
                throw new \Exception("Created repo {$reponame} successfully but it doesn't seem to exist?");
            }

            // Add git remote and push
            shell_exec(
                'cd '.escapeshellarg($git_repo_dir) .' && '.
                'git remote add origin '.escapeshellarg($gitea->gitRemoteUrlFor($reponame)).' && '.
                'git push origin master'
            );

            // Set Gitea topics + description
            $gitea->updateRepoProperties($reponame, $c->shortdesc);
            $gitea->setRepoTopics($reponame, $c->tags);

            echo " Done\n";
        }

    } else {
        usage();
    }
}

main($_SERVER['argv']);