<?php

class CProject {
	
	private $dir;
	
	public $projname;
	public $shortdesc = '(no description)';
	public $subtag = '';
	public $lastupdate = 0;
	public $numreleases = 0;
	
	private $longdesc = '';
	private $prefix_html = '';
	private $images = array();
	private $downloads = array();
	private $downloads_hashes = array();
	
	public $downloads_section_was_replaced = false;
	
	public $lifespan = 0;
	
	public $tags = array();
	
	public $homeimage = null;
	
	protected $go_get_target = '';
	
	public function __construct($dirname, $projname) {
		$this->dir = BASEDIR.'data/'.$dirname.'/';
		$this->projname = $projname;
		
		$matches = [];
		
		// Identify resources in folder
		
		$ls = scandir($this->dir);
		$found_real_lastupdate = false;
		foreach($ls as $file) {
			if ($file[0] == '.') {
				continue;
			}
			
			if ($file == 'README.txt') {
				
				$this->longdesc = file_get_contents($this->dir.'README.txt');
				$this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
				$this->longdesc = preg_replace("~[\s\t]*$~s", "", $this->longdesc); // filter trailing spaces at line endings
								
				// Guess 'last update' time
				if (preg_match('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches)) {
					// Use first date entry (assumed to be a CHANGELOG)
					$this->lastupdate = strtotime($matches[1]);
					$found_real_lastupdate = true;
				}
				
				// Find number of releases
				preg_match_all('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches, PREG_SET_ORDER);
				$this->numreleases = count($matches);
				
				// Find support lifespan (newest minus youngest)
				$eldest = time();
				$newest = 0;
				foreach($matches as $match) {
					$stamp = strtotime($match[1]);
					$eldest = min($stamp, $eldest);
					$newest = max($stamp, $newest);
				}
				$this->lifespan = floor(max(0, $newest - $eldest) / 3600); // could divide by 86400 but it doesn't matter
				
				// Find 'written in'
				if (preg_match('~Written in ([^\\n]+)~', $this->longdesc, $matches)) {
					$this->subtag = rtrim($matches[1], ' .');
				}
				
				// Find `go-get` tags
				$this->longdesc = preg_replace_callback('~\[go-get\](.+)\[/go-get\]~', function($matches) {
					$this->go_get_target = $matches[1];
					return '';
				}, $this->longdesc);
				
				// Find tags
				if (preg_match('~Tags: ([^\\n]+)~', $this->longdesc, $matches)) {
					$this->tags = array_map('trim', explode(',', $matches[1]));
				}
				
				// Extract short description
				$parts = explode("\n", $this->longdesc);
				$this->shortdesc = array_shift($parts);
				$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
				
				// Filters for longdesc	
				
				$prefix_html = '';
				$this->longdesc = preg_replace_callback('~\nWritten in ([^\\n]+)~ms', function($matches) use (&$prefix_html) {
					$prefix_html .= (
						(SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').'&nbsp;' : '').
						mkshield('written in', rtrim($matches[1], '.'), 'blue')
					);
					return '';
				}, $this->longdesc);
				
				while(strpos($this->longdesc, "\n\n\n") !== false) {
					$this->longdesc = str_replace("\n\n\n", "\n\n", $this->longdesc);
				}
				$this->longdesc = rtrim($this->longdesc, "\n")."\n";

				$this->prefix_html = $prefix_html;
				
				continue;
			}
			
			if (! $found_real_lastupdate) {
				$this->lastupdate = max(
					$this->lastupdate,
					// filectime($this->dir.$file),
					($file == 'README.txt' ? filectime($this->dir.$file) : filemtime($this->dir.$file)) // Don't count README updates
				);
			}
						
			if (is_image($file)) {
				$this->images[] = $file;
			} else {
				$this->downloads[] = $file;
			}
		}
		
		natcasesort($this->downloads);
		$this->downloads = array_reverse($this->downloads);
		
		for($i = 0, $e = count($this->downloads); $i !== $e; ++$i) {
			$this->downloads_hashes[] = (
				sha1_file($this->dir.$this->downloads[$i])
			);
		}
		
		
	}
	
	public function genHomeImage() {		
		if (count($this->images)) {
			
			$this->homeimage = mkthumbnail(
				$this->dir.$this->images[0],
				null, // raw handle
				INDEX_THUMB_W, INDEX_THUMB_H
			);
		}
		
	}
	
	protected function filterLongDescArea() {
		
		// If *all* downloads can be assigned to a changelog release entry,
		//  then move the files
		// A release entry is marked by any string following the date field.
		
		// Add one more NL than we really want, for regex reasons
		$this->longdesc .= "\n";
		
		preg_match_all('~^(\d\d\d\d-\d\d-\d\d)\s?:? (.+?)\n\n~ms', $this->longdesc, $matches, PREG_SET_ORDER);
		
		do {
			// Ensure changelog exists
			if (! count($matches)) {
				break;
			}
			
			// Find all tags
			$known_tags = [];
			foreach($matches as $i => $match) {
				$tag = trim(explode("\n", $match[2])[0]);
				$known_tags[$tag] = $i;
			}
			
			// Ensure all release entries have tags
			if (count($matches) != count($known_tags)) {
				error_log("[".$this->projname."] not all release entries have tags\n");
				break;
			}
			
			// Ensure all downloads can be assigned to tags.
			// In the event of a download matching multiple tags, it'll
			//   be assigned to the newest (topmost) entry
			$found_idx = [];
			$render_per_tag = [];
			foreach(array_keys($known_tags) as $tagname) {
				$render_per_tag[$tagname] = [];
			}
			foreach($this->downloads as $idx => $filename) {
				foreach(array_keys($known_tags) as $tagname) {
					if (stripos($filename, $tagname) !== false) {
						$found_idx[$idx] = $tagname;
						
						$render_per_tag[$tagname][$idx] = $filename;
						break; // next file
					}
				}
			}
			if (count($found_idx) != count($this->downloads)) {
				error_log("[".$this->projname."] not all downloads have matching tags");
				break;
			}
			
			// Make HTML modifications
			foreach($known_tags as $tag_name => $tag_idx) {
				$find = rtrim($matches[$tag_idx][0]);
				$this->longdesc = str_replace(
					$find,
					$find.'${{TAG_'.$tag_idx.'}}',
					$this->longdesc
				);
			}
			
			$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
			
			$this->longdesc = text2html($this->longdesc);
			foreach($known_tags as $tag_name => $tag_idx) {
				$this->longdesc = str_replace(
					'${{TAG_'.$tag_idx.'}}',
					$this->renderDownloadsBlock($render_per_tag[$tag_name], false),
					$this->longdesc
				);
			}
			
			$this->longdesc = str_replace("</ul>\n<br />", "</ul>", $this->longdesc);
			
			// Skip displaying the global downloads area
			// This flag also indicates that the content has been pre-HTMLified
			$this->downloads_section_was_replaced = true;
			
			// Successful upgrade
			
		} while(false);
		
		if (! $this->downloads_section_was_replaced) {
			$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
		}
	}
	
	public function numDownloads() {
		return count($this->downloads);
	}
	
	public function write() {
		
		// Generate image thumbnails
		
		foreach($this->images as $idx => $image) {
			$outfile = BASEDIR.'wwwroot/img/'.$this->projname.'_'.$idx;
			copy($this->dir.$image, $outfile.'.'.str_ext($image));
			
			mkthumbnail($outfile.'.'.str_ext($image), $outfile.'_thumb.jpg', PAGE_THUMB_W, PAGE_THUMB_H);
		}
		
		// Copy downloads to wwwroot
		
		foreach($this->downloads as $idx => $filename) {
			$cmkdir = @mkdir( BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx] );
			
			if (! $cmkdir) {
				fputs(
					STDOUT,
					"WARNING: Couldn't create directory ".$this->downloads_hashes[$idx].
					" for file '${filename}'".
					" in project '".$this->projname."'!\n"
				);
			}
			
			copy(
				$this->dir.$filename, 
				BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename
			);
		}
		
		// Generate index page
		
		ob_start();
		$this->index();
		
		$extra_head_items = [];
		
		$extra_head_items[] = '<link rel="canonical" href="'.hesc(BASEURL.$this->projname).'">'; // TODO include golang `go get` meta if necessary
		
		if (strlen($this->go_get_target) > 0) {
			$extra_head_items[] = '<meta name="go-import" content="'.hesc($this->go_get_target).'">'; // TODO include golang `go get` meta if necessary
		}
		
		$idxfile = template($this->projname.' | '.SITE_TITLE, ob_get_clean(), implode("\n", $extra_head_items));
		mkdir(BASEDIR.'wwwroot/'.$this->projname);
		
		file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'/index.html', $idxfile); // new URL format
		file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'.html', redirecthtml(BASEURL.$this->projname)); // old URL format
	}
	
	public function getClassAttr() {
		if (count($this->tags)) {
			return 'taggedWith-'.implode(' taggedWith-', $this->tags);
		} else {
			return '';
		}
	}
	
	public function renderDownloadsBlock($render_downloads, $include_header=false) {
		if (! count($render_downloads)) {
			return;
		}
		
		ob_start();
?>
<?php if ($include_header) { ?>
	<strong>DOWNLOAD</strong>		
<?php } ?> 
	<ul class="<?=$include_header ? 'downloads-large' : 'downloads-small' ?>">
<?php foreach($render_downloads as $idx => $filename) { ?> 
		<li>
			<a href="<?=BASEURL?>srv/<?=hesc($this->downloads_hashes[$idx])?>/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
			<small>
				<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
			</small>
		</li>
<?php } ?> 
	</ul>
<?php
		return ob_get_clean();
	}
	
	public function index() {
		
		$this->filterLongDescArea();
		$longdesc_html = $this->downloads_section_was_replaced ? $this->longdesc : text2html($this->longdesc);
		
?> 
<h2><?=hesc(str_replace('_', ' ', $this->projname))?></h2>

<div class="projinfo">

	<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
		
<?php if (strlen($this->prefix_html)) { ?>
		<p style="margin-top:0;"><?=$this->prefix_html?></p>
<?php } ?> 
		
		<strong><?=hesc(strtoupper(ARTICLE_HEADER))?></strong>

		<div class="content-paragraph">
			<?=$longdesc_html?>
		</div>

		<?=file_get_contents(BASEDIR.'/footer.htm')?> 
		
<?php if (! $this->downloads_section_was_replaced) { ?> 
		<?=$this->renderDownloadsBlock($this->downloads, true)?> 
<?php } ?> 

	</div>

<?php if (count($this->images)) { ?> 
	<div class="projimg">
	<?php foreach($this->images as $idx => $origname) { ?> 
		<a href="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>.<?=str_ext($origname)?>"><img src="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>_thumb.jpg" class="thumbimage"></a>
	<?php } ?> 
	</div>
	
	<div style="clear:both;"></div>
	
<?php } ?> 
	
</div>

<?php
	}
	
}