/*
 *  helpwin.C from ObjectProDSP
 *  Copyright (C) 1994, Mountain Math Software. All rights reserved.
 *  
 *  This file is part of ObjectProDSP, a tool for Digital Signal
 *  Processing design, development and implementation. It is free
 *  software provided you use and distribute it under the terms of
 *  version 2 of the GNU General Public License as published
 *  by the Free Software Foundation. You may NOT distribute it or
 *  works derived from it or code that it generates under ANY
 *  OTHER terms.  In particular NONE of the ObjectProDSP system is
 *  licensed for use under the GNU General Public LIBRARY License.
 *  Mountain Math Software plans to offer a commercial version of
 *  ObjectProDSP for a fee. That version will allow redistribution
 *  of generated code under standard commercial terms.
 *  
 *  This program 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 General Public License for more details.
 *  
 *  You should have received a copy of version 2 of the GNU General
 *  Public License along with this program. See file COPYING. If not
 *  or if you wish information on commercial versions and licensing
 *  write Mountain Math Software, P. O. Box 2124, Saratoga, CA 95070,
 *  USA, or send us e-mail at: support@mtnmath.com.
 *  
 *  You may also obtain the GNU General Public License by writing the
 *  Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *  USA.  However if you received a copy of this program without the
 *  file COPYING or without a copyright notice attached to all text
 *  files, librarys and executables please inform Mountain Math Software.
 *  
 *  ObjectProDSP is a trademark of Mountain Math Software.
 */
#include "helpwin.h"
#include <X11/Xlib.h>
#include <sys/types.h>
#include <sys/stat.h>


#include <IV-look/kit.h>
#include <InterViews/background.h>
#include <InterViews/character.h>
#include <InterViews/composition.h>
#include <InterViews/label.h>
#include <InterViews/layout.h>
#include <InterViews/texcomp.h>
#include <InterViews/scrbox.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/font.h>
#include <InterViews/window.h>
#include <InterViews/resource.h>
#include <InterViews/monoglyph.h>
#include <InterViews/event.h>
#include <OS/file.h>
#include <OS/math.h>
#include <OS/string.h>
#include <stream.h>
#include <stdlib.h>
#include "mkstr.h"
#include "cgidbg.h"
#include "dsp_app.h"
#include "xdrv.h"
#include <string.h>
#include <ctype.h>
#include <X11/keysym.h>


TextWindowView::TextWindowView(Coord x, Coord y, const char ** str):
	the_glyph(0),
	X_dim(x),
	Y_dim(y),
	the_view(0),
	last_strings(0)
{
	// for (const char ** x = str ; *x ; x++) LogOut <<*x << "\n" ;
	replace_page(str);
}


void TextWindowView::replace_page_start()
{
	WidgetKit& kit = *WidgetKit::instance();
	const LayoutKit& layout = *LayoutKit::instance();
	Window * win = 0 ;
	Style * style = 0 ;
	if (the_view) {
		style = the_view->style();
		win = the_view->window();
	} else {
		style = new ivStyle(ivSession::instance()->style());
	}
    the_view = new DocumentView(kit, layout,style,X_dim);
	int deletable = 1 ;
	if (view()) deletable = view()->can_delete();
	if (win) the_view->window(win,0,deletable);
}

void TextWindowView::replace_page_end()
{
	WidgetKit& kit = *WidgetKit::instance();
	const LayoutKit& layout = *LayoutKit::instance();
	the_glyph = layout.hbox(
		new Background(
			layout.variable_span(
				layout.natural_span(
						layout.vcenter(
							the_view, 1.0
						), X_dim, Y_dim
					)
			), kit.background()
		), kit.inset_frame(
			kit.vscroll_bar(
				the_view->adjustable()
			)
		)
	);
}

void TextWindowView::replace_page(const char ** strings)
{
	if (strings == last_strings) return ;
	replace_page_start();
	the_view->replace_page(strings);
	replace_page_end();
	last_strings=strings ;
}

void TextWindowView::replace_page(StringList& string_list)
{
	replace_page_start();
	the_view->replace_page(string_list);
	replace_page_end();
	last_strings=0 ;
}

Glyph * DocumentView::glyph_char[256] = {0} ;


DocumentView::DocumentView(WidgetKit& kit,const LayoutKit& layout,
	Style *style, Coord x, const Font * f):
	DspInputHandler(0,style),
	page_(0),
	box_(0),
	current_strings(0),
	temp_strings(0),
	current_string_size(0),
	temp_size(0),
	X_dim(x),
	deletable(0)
{
	// const Font* f = kit.font();
	// const Font* f = Font::lookup("*helvetica-medium-r-normal--12*");
	if (!f) f = DspApplication::font();
	if (!f) f =  kit.font();
	// LogOut << "f = " << (void *) f << "\n" ;
	const Color* fg = kit.foreground();
	word_space_ = layout.spaces(2, 0.5, f, fg);
	interline_ = layout.vglue();
	vfil_glue_ = layout.vglue();

	String v("justify");
	kit.style()->find_attribute("alignment", v);
	if (v == "left") {
	begin_line_ = layout.vstrut(0);
	end_line_ = layout.strut(f, 0, fil, 0);
	begin_par_ = layout.vstrut(0);
	end_par_ = layout.strut(f, 0, fil, 0);
	} else if (v == "right") {
	begin_line_ = layout.vstrut(0, 0, 0, fil, 0);
	end_line_ = layout.strut(f);
	begin_par_ = layout.vstrut(0, 0, 0, fil, 0);
	end_par_ = layout.strut(f);
	} else if (v == "center") {
	begin_line_ = layout.vstrut(0, 0, 0, fil, 0);
	end_line_ = layout.strut(f, 0, fil, 0);
	begin_par_ = layout.vstrut(0, 0, 0, fil, 0);
	end_par_ = layout.strut(f, 0, fil, 0);
	} else if (v == "justify") {
	begin_line_ = layout.vstrut(0);
	end_line_ = layout.strut(f);
	begin_par_ = layout.vstrut(0);
	end_par_ = layout.strut(f, 0, fil, 0);
	} else {
	// LogOut <<  "Unknown alignment: `" <<  v.string() << "'\n" ;
		// fprintf(stderr, "Unknown alignment: %.*s\n", v.length(), v.string());
		DbgError("DocumentView::DocumentView", "unknown alignment");
	}

	Resource::ref(begin_par_);
	Resource::ref(end_par_);
	Resource::ref(begin_line_);
	Resource::ref(end_line_);
	Resource::ref(word_space_);
	Resource::ref(interline_);
	Resource::ref(vfil_glue_);


	// const Font* f = Font::lookup("*helvetica-medium-r-normal--12*");
	// if (!f) f =  kit.font();
	// const Font* f = kit.font();
	// const Color* fg = kit.foreground();

	if (!glyph_char[0]) {
		for (int i = 0; i < 256; i++) {
			glyph_char[i] = new Character(i, f, fg);
			glyph_char[i]->ref();
		}
		Resource::unref(glyph_char['\n']);

		glyph_char['\n'] = layout.discretionary(
		PenaltyGood,
		end_par_,
		end_par_,
		layout.discretionary(0, interline_, vfil_glue_, nil, nil),
		begin_par_
		);

		Resource::unref(glyph_char[' ']);
		glyph_char[' '] = layout.discretionary(
		0,
		word_space_,
		end_line_,
		layout.discretionary(0, interline_, vfil_glue_, nil, nil),
		begin_line_
		);

		Resource::unref(glyph_char['\t']);
		// glyph_char['\t'] = layout.shape_of(glyph_char['M']);
		glyph_char['\t'] = new Label("		", f, fg);
	}

	make_page(30);

}

DocumentView::~DocumentView() {
	Resource::unref(begin_par_);
	Resource::unref(end_par_);
	Resource::unref(begin_line_);
	Resource::unref(end_line_);
	Resource::unref(word_space_);
	Resource::unref(interline_);
	Resource::unref(vfil_glue_);
}


void DocumentView::make_page(long length)
{
	// LogOut << "make_page\n" ;
	// box_ = new TBScrollBox;
	// box_->small_scroll(2);
	const LayoutKit& layout = *LayoutKit::instance();
    box_ = layout.vscrollbox();
    box_->small_scroll(Dimension_Y, 2);

	page_ = new LRComposition(
	box_, new TeXCompositor(10), nil, 6*72.0, fil, fil, length);
}

void DocumentView::keystroke(const Event&e)
{
	int control = e.control_is_down();
    int shift = e.shift_is_down();
    long sym = e.keysym();
    sym &=0xffffL ;
    switch (sym) {
case XK_Prior:
		adjustable()->page_forward(Dimension_Y);
		break ;
case XK_Next:
		adjustable()->page_backward(Dimension_Y);
		break ;
case XK_Up:
		adjustable()->scroll_forward(Dimension_Y);
		break ;
case XK_Down:
		adjustable()->scroll_backward(Dimension_Y);
		break ;
case XK_Home:
		adjustable()->scroll_to(Dimension_Y,adjustable()->upper(Dimension_Y));
		// LogOut << "lower = " << adjustable()->lower(Dimension_Y) << "\n" ;
		// LogOut << "upper = " << adjustable()->upper(Dimension_Y) << "\n" ;
		// LogOut << "length = " << adjustable()->length(Dimension_Y) << "\n" ;
		break ;
case XK_End:
		adjustable()->scroll_to(Dimension_Y,adjustable()->lower(Dimension_Y));
		// LogOut << "lower = " << adjustable()->lower(Dimension_Y) << "\n" ;
		// LogOut << "upper = " << adjustable()->upper(Dimension_Y) << "\n" ;
		// LogOut << "length = " << adjustable()->length(Dimension_Y) << "\n" ;
		break ;
default:
		DspInputHandler::keystroke(e);
	}
}

void DocumentView::add_page()
{
	page_->append(vfil_glue_);
	page_->repair();
	const LayoutKit& layout = *LayoutKit::instance();
	MonoGlyph * mar_text=layout.margin(page_,10.);
	body(mar_text);
	// LogOut << "Before flush\n" ;
	Resource::flush();
	// LogOut << "After flush\n" ;
}

void DocumentView::replace_file(InputFile * file)
{
	// make_page(file->length());
	const char* data = 0;
	for (;;) {
		int len = file->read(data);
		if (len <= 0) break ;
		if (!data) break ;
/*
 *		LogOut << "DocumentView::replace_file data `" <<
 *			data << "'\n" ;
 */
		// skip initial comment
		int in_comment = 0 ;
		const char * start = data ;
		for (const char * pt=data; pt < data+len; pt++) {
			if (in_comment) if(*pt == '\n') {
					in_comment = 0 ;
					start = pt + 1 ;
/*
 *					LogOut << "DocumentView::replace_file start `" <<
 *						start << "'\n" ;
 */
				} else continue ;
			else if (*pt == '#') in_comment = 1 ;
				else break ;
		}
		len -= start - data ;
		data = start ;
		if (*data) for (char * pt=(char *)data+1; pt < data+len; pt++) 
			if (*pt == '\n') if (isgraph(pt[1]) && (pt[-1] != '\n')
				&& (pt[-1] != '\1') ) *pt = ' ' ;
		add_par(String(data, len));
	}
	add_page();
}

void DocumentView::update_strings()
{
	if (current_strings) for (char **pt = current_strings; *pt; pt++) {
		delete *pt ;
		*pt = 0 ;
	}
	char ** temp = current_strings ;
	current_strings = temp_strings ;
	temp_strings = temp ;
	if (temp_size != current_string_size) {
		delete temp_strings ;
		temp_strings = new char * [current_string_size = temp_size] ;
		for (int i = 0 ;  i < current_string_size ; i++) temp_strings[i] = 0 ;
	};
		
}

void DocumentView::check_size(int size)
{
	if (current_string_size + 1  <= size ) {
		delete temp_strings ;
		temp_strings =
			new char * [temp_size = (int)((10 + size + current_string_size)*1.5)] ;
	}
}


void DocumentView::replace_page(StringList &the_strings)
{
	check_size(the_strings.Size());
	page_->append(vfil_glue_);
	page_->repair();
	char * str ;
	int ix = 0 ; 
	int total_length = 0 ;
	int too_long =  0 ;
	while (str = the_strings.Get()) {
		if (!*str) continue ;
		total_length += strlen(str);
		if (total_length > 100000) if (!too_long) {
			add(String("Window truncated at 100000 characters."));
			too_long = 1 ;
		}
		temp_strings[ix++] = str ;
		if (!too_long) add(String(str));
		
	}
	temp_strings[ix] = 0 ;
	
	add(String("\n"));
	add_page();
	update_strings();
}

void DocumentView::replace_page(const char ** strings)
{
	int size = 0 ;
	for (const char ** str = strings; *str; str++) size++ ;
	check_size(size);
	page_->append(vfil_glue_);
	page_->repair();
	// for(int length=0; strings[length];length++);
	// make_page(length*2);
	int total_length = 0 ;
	int too_long =  0 ;
	int ix = 0 ;
	for (const char ** pt =strings;*pt;pt++) {
		if (pt != strings) add(String(" "));
		total_length += strlen(*pt);
		if (total_length > 100000) if (!too_long) {
			add(String("Window truncated at 100000 characters."));
			too_long = 1 ;
		}
		if (!too_long) add(String(*pt));
		temp_strings[ix++] = (char *) *pt ;
	}
	temp_strings[ix] = 0;
	add(String("\n"));
	add_page();
	update_strings();
}

void DocumentView::add_par(const String& data)
{
	page_->append(begin_par_);
	add(data);
}

/*
 * void DocumentView::add(const String& data) {
 *	const char* p = data.string();
 *	const char* end = p + data.length();
 *
 *	// LogOut << "Appending `" << p << "'\n" ;
 *	for (; p < end; p++) {
 *	page_->append(glyph_char[*p]);
 *	}
 * }
 */

void DocumentView::draw(Canvas* c, const Allocation& a) const
{
	// LogOut << "ocumentView::draw, X_dim = " << X_dim << "\n" ;
	Allotment x = a.x_allotment();
	Coord left = a.left();
	Coord right = a.right() ;
	((DocumentView *) this)->X_dim = right - left ;
	DspInputHandler::draw(c,a);
	// LogOut << "ocumentView::draw - exit,  X_dim = " << X_dim << "\n" ;
}

void DocumentView::add(const String& data) {
	const char* p = data.string();
	const char* end = p + data.length();
	Coord contiguous_length = 0 ;
	Coord width = X_dim * .97 ;
	// LogOut << "width = " << width << "\n" ;
	Coord min_width = 10 * DspApplication::x_req(glyph_char['x']);
	if (width < min_width) width = min_width ;
	// LogOut << "width = " << width << "\n" ;
	int write_eol = 0 ;
	const char * last_out = p ;
	int last_char_was_eol = 0 ;
	for (; p < end ; p++) {
		int write_out = 0 ;
		if (isspace(*p)) write_out = 1 ;
		else {
			contiguous_length+=DspApplication::x_req(glyph_char[*p]);
			if (contiguous_length > width * .9 ) write_out = write_eol = 1 ;
			else if (contiguous_length > width * .45 ) write_eol = 1 ;
		}
		if (p == end -1) write_out = 1 ;        
/*
 *		LogOut << "DV::Add:`" << *p << "', cg = " << contiguous_length 
 *			<< ", w_o = " << write_out << ", eol = " << write_eol << "\n";
 */
		if (write_out) {
			if (write_eol) if (!last_char_was_eol)
				page_->append(glyph_char['\n']);
			for (; last_out <=p; last_out++)
				page_->append(glyph_char[*last_out]) ;
			last_char_was_eol = 0 ;
			if (write_eol) {
				page_->append(glyph_char['\n']);
				last_char_was_eol = 1 ;
			}
			write_eol = 0 ;
			contiguous_length = 0 ;
		}
	}
}

Adjustable* DocumentView::adjustable() const {
	return box_;
}


TextWindowView * TextWindowKit::document_window(Coord X_dim, Coord Y_dim,
	const char **str)
{
/*
 *	LogOut << "TextWindowKit::document_window(" << X_dim << ", " << Y_dim
 *		<< ")\n" ;
 */
	return new TextWindowView(X_dim,Y_dim,str);
}

TopLevelWindow * TextWindowKit::document_view(const char * file_name,
	const char * win_name, const Font * font)
{
	// LogOut << "TextWindowKit::document_view(\"" << file_name << "\")\n" ;
	WidgetKit& kit = *WidgetKit::instance();
	const LayoutKit& layout = *LayoutKit::instance();
	struct stat status ;
	int err = stat(file_name,&status);
	if (err) {
		HelpOut << "Cannot get status of `" << file_name << "'\n" ;
		char * str ;
		SystemErrorMessage(0,&str);
		HelpOut << str << "\n" ;
		delete str ;
		return 0 ;
	}
	if (status.st_size > 100000) {
		HelpOut << "File `" << file_name << "' is too large (" <<
			status.st_size << " bytes)\n" ;
		HelpOut <<
			"for this type of window. 100000 bytes is the maximum allowed.\n" ;
		return 0 ;
	}
	InputFile* file = InputFile::open(file_name);
	if (file == nil)  return 0 ;
	Style * style = new ivStyle(ivSession::instance()->style());
	char * delete_name = 0 ;
	if (!win_name) {
		char * name = RemoveSuffix(file_name,".hlp");
		win_name = delete_name = OpdRemoveDirectory(name);
		delete name ;
	}
	char * delete_win_name = Concatenate("help ",win_name);
    style->attribute("name",delete_win_name);
    style->attribute("iconName",delete_win_name);
	delete delete_name ;
	delete delete_win_name ;

	Coord X_dim = 1.70*4*72.0 ;
	DocumentView* view = new DocumentView(kit, layout, style,X_dim,font);
	view->replace_file(file);
	TopLevelWindow * Return = new TopLevelWindow(
		layout.hbox(
		kit.inset_frame(
			kit.vscroll_bar(view->adjustable())
		),
		kit.inset_frame(
			new Background(
				layout.variable_span(
						layout.natural_span(
							layout.vcenter(view, 1.0), X_dim, 4*72.0
						)
					),kit.background()
				)
			)
		)
	);
	Return->style(style);
	Return->group_leader(Return);
	view->window(Return,0);
	return Return ;
}

void TextWindowKit::warn_info_window(Coord X_dim,
	Coord Y_dim, const char **str)
{
	TextWindowView * warn =  new TextWindowView(X_dim,Y_dim,str);
	Style * style = new ivStyle(ivSession::instance()->style());
	const LayoutKit& layout = *LayoutKit::instance();
	WidgetKit& kit = *WidgetKit::instance();
	style->attribute("name", "warning");
    style->attribute("iconName", "warning");
    ivGlyph * help_frame = kit.inset_frame(layout.
    	margin(warn->glyph(),5.0));
          
	TopLevelWindow * info_window = new ivTopLevelWindow(help_frame);
	info_window->style(style) ;
	warn->view()->window(info_window,0); // this allows window to be deleted
	info_window->map() ;
	for (int i = 0;i<1000;i++) while(DspApplication::event_check());
}

