/******************************************************************************
 GMTextLink.cc

	Implements algorithms required to make portions of the text act
	like buttons.

	Derived classes must implement:

		GetLinkCount
			Returns the number of links in the text.

		GetLinkRange
			Returns the character range of the specified link.

		LinkClicked
			Take whatever action is appropriate for the specified link.

	BASE CLASS = JXTEBase

	Copyright © 1998 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <GMTextLink.h> 

#include <JXDisplay.h>
#include <JXHelpManager.h>

#include <JXTEBase.h>
#include <JRegex.h>
#include <JString.h>
#include <JColormap.h>
#include <JLatentPG.h>
#include <JFontManager.h>

#include <jXGlobals.h>

#include <jAssert.h>

static const JRegex kGMURLRegex
	(
	"(http://[^][:space:]\'`;:()<>[\"]+)|"
	"(ftp://[^][:space:]\'`;:()<>[\"]+)|"
	"(mailto:[^][:space:]\'`;:()<>[\"]+)|"
	"([^][:space:]@\'`;:<>[\"]+@[^][:space:]@\'`;:()<>[\"]+)|"
	"(www\\.[^][:space:]\'`;:()<>[\"]+)"
	);

static const GMTextLink::LinkType kGMLinkTypes[] = 
	{
	GMTextLink::kFullURL,
	GMTextLink::kEMail,
	GMTextLink::kWeb
	};

static const JIndex kGMLinkTypesStartOffset	= 2;
static const JIndex kGMLinkTypesLength		= 6;

#define LEGAL_CHAR "^][:space:]\'`;<>[\""
#define LEGAL_HOST "[" LEGAL_CHAR "()]*[" LEGAL_CHAR "().,:]"

static const JRegex kGMRegex1("(http://|ftp://|mailto:)" LEGAL_HOST);
static const JRegex kGMRegex2("[" LEGAL_CHAR "@]+@" LEGAL_HOST);
static const JRegex kGMRegex3("www\\." LEGAL_HOST);

static const JRegex* kGMRegexes[]	= {&kGMRegex1, &kGMRegex2, &kGMRegex3};

static const JSize kGMRegexCount 	= sizeof(kGMRegexes)/sizeof(JRegex*);

const JCharacter* regexStr  		= "(http://|ftp://|mailto:)[^][:space:]\'`;:()<>[\"]+";
const JCharacter* regexStr2 		= "[^][:space:]@\'`;:<>[\"]+@[^][:space:]@\'`;:()<>[\"]+";
const JCharacter* regexStr3 		= "www\\.[^][:space:]\'`;:()<>[\"]+";

const JIndex kGMRemoveAllLinks		= 1;

/******************************************************************************
 Constructor

 ******************************************************************************/

GMTextLink::GMTextLink
	(
	JXDisplay*	display,
	JXTEBase* 	editor
	)
	:
	JXTextLink(display),
	JBroadcaster(),
	itsEditor(editor)
{
	itsLinks = new JArray<LinkInfo>;
	assert(itsLinks != NULL);

	ListenTo(itsEditor);
}

/******************************************************************************
 Destructor

 ******************************************************************************/

GMTextLink::~GMTextLink()
{
	RemoveLinks(kGMRemoveAllLinks);
	delete itsLinks;
}

/******************************************************************************
 GetLinkCount (protected)

 ******************************************************************************/

JSize
GMTextLink::GetLinkCount()
	const
{
	return itsLinks->GetElementCount();
}

/******************************************************************************
 GetLinkRange (protected)

 ******************************************************************************/

JIndexRange
GMTextLink::GetLinkRange
	(
	const JIndex index
	)
	const
{
	return (itsLinks->GetElement(index)).range;
}

/******************************************************************************
 LinkClicked (protected)

 ******************************************************************************/

void
GMTextLink::LinkClicked
	(
	const JIndex index
	)
{
	const LinkInfo info = itsLinks->GetElement(index);
	JString url = *(info.url);
	if (info.type == kEMail)
		{
		url.Prepend("mailto:");
		}
	else if (info.type == kWeb)
		{
		url.Prepend("http://");
		}
	(JXGetHelpManager())->ShowURL(url, NULL);
}

/******************************************************************************
 Receive (protected)

 ******************************************************************************/

void
GMTextLink::Receive
	(
	JBroadcaster* 	sender, 
	const Message& 	message
	)
{
	if (sender == itsEditor && message.Is(JTextEditor::kTextSet))
		{
		RemoveLinks(kGMRemoveAllLinks);
		JLatentPG pg(50);
		JSize length = itsEditor->GetTextLength();
		pg.FixedLengthProcessBeginning((kGMRegexCount + 1)*length, "Parsing URLs...", kTrue, kFalse);
		JIndex lastindex = 0;
		JBoolean cancel = kFalse;
		if (length > 0)
			{
			JBoolean ok;
			for (JSize i = 0; i < kGMRegexCount; i++)
				{
				ok = ParseForURLs(i, &pg, length, &lastindex);
				if (!ok)
					{
					cancel = kTrue;
					break;
					}
				}
			}
		const JColorIndex blueColor = JGetCurrColormap()->GetBlueColor();

		JSize linkCount	= itsLinks->GetElementCount();
		JSize lastLink 	= linkCount;
		if (cancel)
			{
			RemoveLinks(kGMRemoveAllLinks);
			}
		else if (linkCount > 0)
			{
//			const JRunArray<JTextEditor::Font>& oldfonts	= itsEditor->GetStyles();
//			JRunArray<JTextEditor::Font> newfonts;
//
//			JTextEditor::Font regular		= oldfonts.GetElement(1);
//			JTextEditor::Font link			= regular;
//			const JFontManager*	fm			= itsEditor->GetFontManager();
//			link.style.color				= blueColor;
//			link.style.underlineCount		= 1;
//			link.id							= fm->UpdateFontID(link.id, link.size, link.style);
//			JIndex lastIndex				= 0;

			for (JSize i = 1; i <= linkCount; i++)
				{
				const LinkInfo info = itsLinks->GetElement(i);
//				JSize count			= info.range.first - lastIndex - 1;
//				lastIndex			= info.range.last;
//				if (count > 0)
//					{
//					newfonts.AppendElements(regular, count);
//					}
//				newfonts.AppendElements(link, info.range.GetLength());
				itsEditor->SetSelection(info.range);
				itsEditor->SetCurrentFontColor(blueColor);
				itsEditor->SetCurrentFontUnderline(1);
				if (!pg.IncrementProgress((kGMRegexCount*length + info.range.last) - lastindex))
					{
					cancel = kTrue;
					lastLink = i;
					break;
					}
				lastindex = kGMRegexCount*length + info.range.last;
				}
//			JSize leftOverTextCount = length - lastIndex;
//			if (leftOverTextCount > 0)
//				{
//				newfonts.AppendElements(regular, leftOverTextCount);
//				}
//			StopListening(itsEditor);
//			itsEditor->SetText(itsEditor->GetText(), &newfonts);
//			ListenTo(itsEditor);
			if (lastLink != linkCount)
				{
				RemoveLinks(lastLink + 1);
				}
			if (itsEditor->GetTextLength() > 0)
				{
				itsEditor->SetCaretLocation(1);
				}
			}
		pg.ProcessFinished();
		}
}

/******************************************************************************
 ParseForURLs (private)

 	index is zero based because it refers to a c-array.

 ******************************************************************************/

JBoolean
GMTextLink::ParseForURLs
	(
	const JIndex 	index,
	JLatentPG* 		pg,
	const JSize 	length,
	JIndex*			lastindex
	)
{
	JBoolean ok = kTrue;
	JBoolean wrapped;
	JArray<JIndexRange> submatchList;	
	while (ok && itsEditor->SearchForward(*kGMRegexes[index], kFalse, 
									kFalse, &wrapped, &submatchList))
		{
		JIndexRange range = submatchList.GetElement(1);
		JString* str = new JString(itsEditor->GetText().GetSubstring(range));
		assert(str != NULL);
		LinkInfo info(range, str, kGMLinkTypes[index]);
		itsLinks->AppendElement(info);
		if (!pg->IncrementProgress((index * length) + range.last - *lastindex))
			{
			ok = kFalse;
			}
		*lastindex = (index * length) + range.last;
		}
	itsEditor->SetCaretLocation(1);
	JSize delta = ((index + 1) * length) - *lastindex;
	*lastindex = (index + 1) * length;
	if ((delta != 0) && !pg->IncrementProgress(delta))
		{
		ok = kFalse;
		}
	return ok;
}

/******************************************************************************
 RemoveLinks (private)

 ******************************************************************************/

void
GMTextLink::RemoveLinks
	(
	const JIndex startIndex
	)
{
	JBoolean removeAll = JI2B(startIndex == kGMRemoveAllLinks);
	JSize linkCount = itsLinks->GetElementCount();
	for (JSize i = linkCount; i >= startIndex; i--)
		{
		LinkInfo info = itsLinks->GetElement(i);
		delete info.url;	
		if (!removeAll)
			{
			itsLinks->RemoveElement(i);
			}
		}
	if (removeAll)
		{
		itsLinks->RemoveAll();
		}
}


#define JTemplateType GMTextLink::LinkInfo
#include <JArray.tmpls>
#undef JTemplateType

