<?php namespace HashOver;

// Copyright (C) 2015-2019 Jacob Barkdull
// This file is part of HashOver.
//
// HashOver is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// HashOver is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with HashOver. If not, see <http://www.gnu.org/licenses/>.


class Markdown
{
// Matches a markdown code block
protected $blockCodeRegex = '/```([\s\S]+?)```/S';

// Matches a paragraph/double line break
protected $paragraphRegex = '/(?:\r\n|\r|\n){2}/S';

// Matches markdown inline code
protected $inlineCodeRegex = '/(^|[^a-z0-9`])`((?!`)[\s\S]+?)`([^a-z0-9`]|$)/iS';

// Array for inline code and code block markers
protected $codeMarkers = array (
'block' => array ('marks' => array (), 'count' => 0),
'inline' => array ('marks' => array (), 'count' => 0)
);

// Markdown patterns to search for
protected $search = array (
// Matches **bold** text
'/\*\*([^ *])([\s\S]+?)([^ *])\*\*/S',

// Matches *italic* text
'/\*([^ *])([\s\S]+?)([^ *])\*/S',

// Matches _underlined_ text
'/(^|\W)_((?!_)[\s\S]+?)_(\W|$)/S',

// Matches forced __underlined__ text
'/__([^ _])([\s\S]+?)([^ _])__/S',

// Matches ~~strikethrough~~ text
'/~~([^ ~])([\s\S]+?)([^ ~])~~/S'
);

// HTML replacements for markdown patterns
protected $replace = array (
'<strong>\\1\\2\\3</strong>',
'<em>\\1\\2\\3</em>',
'\\1<u>\\2</u>\\3',
'<u>\\1\\2\\3</u>',
'<s>\\1\\2\\3</s>'
);

// Replaces markdown for inline code with a marker
protected function codeReplace ($grp, $display)
{
$markName = 'CODE_' . strtoupper ($display);
$markCount = $this->codeMarkers[$display]['count']++;

if ($display !== 'block') {
$codeMarker = $grp[1] . $markName . '[' . $markCount . ']' . $grp[3];
$this->codeMarkers[$display]['marks'][$markCount] = trim ($grp[2], "\r\n");
} else {
$codeMarker = $markName . '[' . $markCount . ']';
$this->codeMarkers[$display]['marks'][$markCount] = trim ($grp[1], "\r\n");
}

return $codeMarker;
}

// Replaces markdown for code block with a marker
protected function blockCodeReplace ($grp)
{
return $this->codeReplace ($grp, 'block');
}

// Replaces markdown for inline code with a marker
protected function inlineCodeReplace ($grp)
{
return $this->codeReplace ($grp, 'inline');
}

// Returns the original inline markdown code with HTML replacement
protected function inlineCodeReturn ($grp)
{
return '<code class="hashover-inline">' . $this->codeMarkers['inline']['marks'][($grp[1])] . '</code>';
}

// Returns the original markdown code block with HTML replacement
protected function blockCodeReturn ($grp)
{
return '<code>' . $this->codeMarkers['block']['marks'][($grp[1])] . '</code>';
}

// Parses a string as markdown
public function parseMarkdown ($string)
{
// Reset marker arrays
$this->codeMarkers = array (
'block' => array ('marks' => array (), 'count' => 0),
'inline' => array ('marks' => array (), 'count' => 0)
);

// Replace code blocks with markers
$string = preg_replace_callback ($this->blockCodeRegex, 'self::blockCodeReplace', $string);

// Break string into paragraphs
$paragraphs = preg_split ($this->paragraphRegex, $string);

// Run through each paragraph
for ($i = 0, $il = count ($paragraphs); $i < $il; $i++) {
// Replace inline code with markers
$paragraphs[$i] = preg_replace_callback ($this->inlineCodeRegex, 'self::inlineCodeReplace', $paragraphs[$i]);

// Replace markdown patterns
$paragraphs[$i] = preg_replace ($this->search, $this->replace, $paragraphs[$i]);

// Replace markers with original markdown code
$paragraphs[$i] = preg_replace_callback ('/CODE_INLINE\[([0-9]+)\]/S', 'self::inlineCodeReturn', $paragraphs[$i]);
}

// Join paragraphs
$string = implode (PHP_EOL . PHP_EOL, $paragraphs);

// Replace code block markers with original markdown code
$string = preg_replace_callback ('/CODE_BLOCK\[([0-9]+)\]/S', 'self::blockCodeReturn', $string);

return $string;
}
}