#include <InterViews/layout.h>
#include <InterViews/label.h>
#include <InterViews/glyph.h>
#include <InterViews/style.h>
#include <InterViews/font.h>
#include <InterViews/event.h>
#include "txtwin.h"
#include <IV-look/kit.h>
#include <X11/keysym.h>
#include <unistd.h>
#include "cgidbg.h"
#include "dsp_app.h"
#include "file_view.h"

/*-----------------------------------------*/
#include <InterViews/adjust.h>
#include <InterViews/session.h>
#include <OS/string.h>
#include <stdio.h>
#include "portable.h"
#include <InterViews/background.h>
#include "genmenu.h"
#include <InterViews/enter-scope.h>
#include <strstream.h>
#include <fstream.h>
#include <limits.h>
#include <ctype.h>
#include "mkstr.h"
#include "slist.h"

// void TestAlloc(const char * msg=0);

class ActBackground: public InputHandler {
	DspInputHandler& in ;
public:
	ActBackground(Glyph* g, Style *s, DspInputHandler& h):
		InputHandler(g,s),in(h){}
	virtual void keystroke(const Event&e) {in.keystroke(e);}
};

class DisplayedLines: public MonoGlyph {

	Coord last_height ;
	FileView& view ;
	PolyGlyph& lines ;
public:
	DisplayedLines(FileView& v, const ivColor *);
	virtual void allocate(Canvas *, const Allocation&, Extension& e)  ;
	GlyphIndex b_count() {return lines.count();}
	void b_remove(GlyphIndex i) {lines.remove(i);}
	void b_append(Glyph* a) {lines.append(a);}
	Coord height();
	static Coord margin ;
	int force_size(const Font*);
};

Coord DisplayedLines::margin = 7 ;

DisplayedLines::DisplayedLines(FileView& v, const ivColor * color):
		view(v),
		lines(*(view.layout().vbox())),
		last_height(-1)
{
	body(
		new Background(
			view.layout().margin(
				view.layout().vbox(
						&lines,
						view.layout().vglue()
				), margin
			),color
		)
	);
}

int DisplayedLines::force_size(const Font * font)
{
	if (b_count() == view.lines()) return 0 ;
	while(b_count() < view.lines()) 
		b_append(
			view.layout().hbox(
				new Label(
					DspApplication::spacer(),font,DspApplication::black()
				),
				view.layout().hglue()
			)
		);
		return 1 ;
}

void DisplayedLines::allocate(Canvas *c, const Allocation&a, Extension& e) 
{
	Coord h = a.top() - a.bottom();
/*
 *	LogOut << "DisplayedLines::allocate, last_height = " << last_height <<
 *		", h = " << h << "\n" ; 
 */
	if (h != last_height) {
		last_height = h ;
		view.resize();
	}
	MonoGlyph::allocate(c,a,e);
}

Coord DisplayedLines::height()
{
	if (last_height > -1) return last_height - 2 * margin ;
	return view.Y_dim - 2 * margin ;
}

struct CacheEntry {
	int32 line ;
	int32 byte ;
};



class CachedFile {
	CacheEntry * entries ;
	int used ;
	int last_entry ;
	int skipped_comment_bytes ;
	const char * name ;
	FILE * file;

	void add(int32 lines, int32 bytes);
    int add_if_space(int32 lines, int32 bytes);
    int add_if_far(int32 lines, int32 bytes,int32 diff, float fact = .25);
	void check_add_last();

	int32 next_byte_to_write ;
	int32 next_line_to_write ;
	int32 last_byte_in_cache ;
	int32 current_byte ;
	int32 current_line ;
	FileView *view ; // for debugging

	int address(int i) ; // address of ith most recent queue entry
	void find_line(int32 line,int32 smaller_line, int32 smaller_byte);
public:
	const size ;
	CachedFile(const char * name,int s,FileView * v=0);
	void init();
	void line_seek(int32 line);
	void byte_seek(int32 byte);
	const char * fgets(char * buf, int32 buf_size);
	void update_size(const char * str,int extra_lines=0);
	void update_size(int32 lines, int32 bytes) ;
	int32 last() const {return next_line_to_write;}
	const char * file_name() const {return name;}
	void mark_current_bad() {current_line = -1;}
};

int CachedFile::address(int i)
{
	int ret ;
/*
 *	LogOut << "CachedFile::address(" << i << "), lst = " << last_entry <<
 *		", sz = " << size << ", usd = " << used ;
 */
	if (i < 0) DbgError("CachedFile::address","negative");
	if (i <= last_entry) ret = last_entry - i ;
	else {
		ret = size - i + last_entry ;
		if (ret >= used) ret = -1 ;
		else if (ret <= last_entry) ret = -1 ; // have wrapped 
	}
	// LogOut << " returns " << ret << "\n" ;
	return ret ;
}

CachedFile::CachedFile(const char * n,int s, FileView *v):
	name(n),
	size(s),
	used(0),
	last_entry(-1),
	current_byte(0),
	current_line(0),
	next_byte_to_write(0),
	next_line_to_write(0),
	file(0),
	entries(new CacheEntry[s]),
	view(v),
	skipped_comment_bytes(0),
	last_byte_in_cache(0)
{
	file = fopen(n,"r");
	if (!file) DbgError("CachedFile::CachedFile","cannot open file");
	const buf_size = 8192 ;
	char buf[buf_size+1] ;
	while (::fgets(buf,buf_size,file)) {
		if (*buf == '#') skipped_comment_bytes += strlen(buf);
		else break ;
	}
	byte_seek(0);
}

void CachedFile::init()
{
	const buf_size = 4096 ;
	char buf[buf_size+1];
	byte_seek(0);
	for(;;) {
		int read = fread(buf,sizeof(char),buf_size,file);
		if (read < 1) break ;
		buf[read] ='\0' ;
		update_size(buf);
	}
	byte_seek(0);
}

void CachedFile::check_add_last()
{
	
	add_if_far(next_line_to_write,next_byte_to_write,next_byte_to_write-
		last_byte_in_cache,2);
}

void CachedFile::update_size(int32 lines, int32 bytes)
{
	next_byte_to_write+=bytes;
	next_line_to_write+=lines;
	check_add_last();
}

void CachedFile::update_size(const char * str,int extra_lines)
{
/*
 *	LogOut << "CachedFile::update_size(," << extra_lines << "), l = " <<
 *			next_line_to_write << ", b = " << next_byte_to_write << "\n" ;
 */
	for (const char *pt=str;*pt;pt++,next_byte_to_write++) if (*pt == '\n')
		next_line_to_write++;
	next_line_to_write+= extra_lines ;
	next_byte_to_write+= extra_lines ;
	check_add_last();
/*
 *	LogOut << "update_size exit: l = " << next_line_to_write << ", b = " <<
 *		next_byte_to_write << "\n" ;
 */
}

int CachedFile::add_if_space(int32 lines, int32 bytes)
{
	if (used >= size) return 0 ;
	add(lines,bytes);
	return 1;
}

int CachedFile::add_if_far(int32 lines, int32 bytes, int32 dist, float fact)
{
	if (next_line_to_write) if ((used >= size) &&
		(dist < (fact*next_byte_to_write/(next_line_to_write * size)))) return 0 ;
	add(lines,bytes);
	return 1;
}


void CachedFile::add(int32 lines, int32 bytes)
{
		if (++last_entry >= size) last_entry = 0 ;
		if (used < size) used++ ;
		entries[last_entry].line = lines ;
		entries[last_entry].byte = bytes ;
		if (bytes > last_byte_in_cache) {
			last_byte_in_cache = bytes ;
			// LogOut << "last_byte_in_cache = " << last_byte_in_cache << "\n" ;
		}
/*
 *		LogOut << "last_entry = " << last_entry << ", l = " << lines
 *			<< ", b = " << bytes << "\n" ;
 */
}

const char * CachedFile::fgets(char * buf, int32 buf_size)
{
	const char * ret = ::fgets(buf,buf_size,file);
	if (!ret) return ret ;
	for (const char * pt = buf; *pt;pt++) if (*pt == '\n') {
		current_byte += pt - buf + 1 ;
		current_line++ ;
		return ret ;
	}
	current_byte = current_line = -1 ;
	return ret ;
}

void CachedFile::byte_seek(int32 byte)
{
	// LogOut << "CachedFile::byte_seek(" << byte << ")\n" ;
	if (fseek(file,byte+skipped_comment_bytes,SEEK_SET)) DbgError("CachedFile::seek",
            "seek failed");
}

void CachedFile::find_line(int32 line,int smaller_line, int smaller_byte)
{
	const buf_size = 8192 ;
	char buf [buf_size];
/*
 *	LogOut << "find_line(" << line << ", " << smaller_line << ", "
 *			<< smaller_byte << ")\n" ;
 */
	if (current_line > -1) if (current_line == line) return ;
	if (smaller_line < 0 || smaller_byte < 0) 
		smaller_line= smaller_byte = 0 ;
	byte_seek(smaller_byte);
	current_line = smaller_line ;
	current_byte = smaller_byte ;
	if (!line) return ;
	for(;;) {
		int read = fread(buf,sizeof(char),buf_size,file);
		if (read <=0) DbgError("CachedFind::find","not_found");
		// LogOut << "Read " << read << "\n" ;
		int total = 0 ;
		for (int i = 0; i < read;i++) if (buf[i] == '\n') {
			current_line++;
			if (current_line == line) {
				current_byte += i + 1 ;
				total += i + 1 ;
				// LogOut << "found at " << current_byte << "\n" ;
				if (i < read-1) byte_seek(current_byte);
				add_if_far(current_line,current_byte,total);
				return ;
			}
		}
		current_byte += read ;
		total += read ;
	}
}

void CachedFile::line_seek(int32 line)
{
/*
 *	LogOut << "CachedFile::line_seek(" << line << "), curr = "
 *		<< current_line << ", nxt = " << next_line_to_write << "\n" ;
 */
	if (line == current_line) return ;
	if (line == 0) {
		byte_seek(current_byte = current_line = 0);
		return ;
	}
	if (line == next_line_to_write) {
		byte_seek(next_byte_to_write);
		current_line=next_line_to_write ;
		current_byte=next_byte_to_write ;
		return ;
	}
	int adr ;
	// LogOut << "searching cache for " << line << "\n" ;
	int32 largest_smaller_byte = 0 ;
	int32 largest_smaller_line = 0 ;
	for (int i = 0;(adr=address(i)) >=0; i++) {
		int32 l = entries[adr].line;
		if (line == l) break ;
		int32 b = entries[adr].byte ;
		if ((l < line) && (largest_smaller_byte < b)) {
			largest_smaller_byte = b ;
			largest_smaller_line = l ;
		}
	}
	if (adr >= 0) {
		// LogOut << "found at " << i << " (" << adr << ")\n" ;
		int32 byte = entries[adr].byte ;
		// LogOut << "byte is " << byte << "\n" ;
		if (i) { // move to top of cache
			for (int j = i; j>0; j--) entries[address(j)] = entries[address(j-1)];
			int a = address(0);
			entries[a].line = line ;
			entries[a].byte = byte ;
		}
		byte_seek(byte);
		return ;
	}
	find_line(line,largest_smaller_line,largest_smaller_byte);
}
	


Coord FileView::Y_dim = 200. ;

FileView::FileView(WidgetKit& k, const LayoutKit& l, Style& s,const char * nm,
	const Font * f, const Color * bg,TxtWindow * t):
		DspActiveHandler(0,&s),
		the_style(s),
		the_kit(k),
		file_name(nm),
		the_layout(l),
		font(f),
		current_line(-1),
		in_adjust_adjuster(0),
		in_update(0),
		box(),
		y_size(0),
		the_menu_keyboard(0),
		line_count(-1),
		prompt_value_return(0),
		got_value(0),
		clear_flag(0),
		notify_on_exit(t),
		last_not_terminated(0),
		the_cache(new CachedFile(nm,50,this))
{
	if (!font) font = DspApplication::font();
	box.attach(Dimension_X, this);
	the_menu_keyboard = build_menu(&k,l,&s);
	DspApplication::dimensions(
		new Label(
			DspApplication::spacer(), font,DspApplication::black()
		),X_dim,y_size
	);
	
	the_lines = new DisplayedLines(*this,bg);
	the_cache->init();
	resize();
	build(bg);
	clear_flag = the_lines->force_size(font);
	focus(this);
}

FileView::~FileView()
{
	delete the_cache ;
}

void FileView::exit_window()
{
	if (notify_on_exit) notify_on_exit->exit_history_window();
	FocusSelector::exit_window();
}

void FileView::build(const Color * bg)
{
	if (!bg) bg = DspApplication::white();
	// LogOut << "FileView::build\n" ;
	the_glyph =  new Background(
		the_kit.inset_frame(
			the_layout.vbox(
				the_menu_keyboard,
				the_layout.hbox(
					the_kit.inset_frame(the_lines),
					the_kit.vscroll_bar(
						adjustable()
					)
				)
			)
		),bg
	);
	body(the_glyph);
}

void FileView::resize()
{
	// LogOut << "FileView::resize(" << the_lines->height() << ")\n" ;
	int new_count = (int) (the_lines->height()/y_size) ;
	if (new_count == line_count) return ;
	int old_line_count = new_count ;
	line_count = new_count ;
	// LogOut << "line_count = " << line_count << "\n" ;
	int32 save_current = current_line ;
	if (old_line_count > 0) if (line_count > old_line_count) {
		the_cache->line_seek(current_line + old_line_count);
		fill();
		adjust_adjuster();
		return ;
	} else if (line_count < old_line_count) {
		int limit = old_line_count - line_count ;
        for (GlyphIndex i = the_lines->b_count() - 1; limit-- > 0 ; i--)
			the_lines->b_remove(i);
	}
	current_line = -1 ;
	fill(save_current);
	adjust_adjuster();
}

void FileView::update(Observable*)
{
	if (in_update || in_adjust_adjuster) return ;
	int new_line = (int) (box.cur_lower(Dimension_X) + .5) ;
	// LogOut << "FileView::update, new_line = " << new_line << "\n" ;
	int end_limit = the_cache->last() - line_count;
	// LogOut << "c->l = " << the_cache->last() << ", lc = " << line_count << "\n";
	if (end_limit < 0) end_limit = 0 ;
	new_line = end_limit - new_line ;
	if (new_line < 0)  new_line = 0 ;
	if (new_line > end_limit) new_line = end_limit ;
	// LogOut << "end = " << end_limit << ", new_line =" << new_line << "\n" ;
	in_update = 1 ;
	fill(new_line);
	in_update = 0 ;
	// LogOut << "FileView::update exit\n" ;
}

void FileView::adjust_adjuster()
{
	in_adjust_adjuster = 1 ;
	// LogOut << "FileView::adjust_adjuster, in_update = " << in_update << "\n" ;
	int end_limit = the_cache->last() - line_count;
/*
 *	LogOut << "adjust_adjuster(), current = " << current_line <<
 *		", l = " << the_cache->last() << ", ct = " << line_count << 
 *		", e = " << end_limit << "\n" ;
 */
	if (end_limit < 0) end_limit = 0 ;
	// box.upper_bound(end_limit);
	box.upper_bound(the_cache->last());
	int scroll_increment = 1 ;
	int page_increment = line_count ;
	box.window_size(line_count);
	box.scroll_incr(scroll_increment);
    box.page_incr(page_increment);
	int cur = end_limit - current_line ;
	if ( cur < 0) cur = 0 ;
    box.current_value(cur);
	in_adjust_adjuster = 0 ;
}

void FileView::line_down()
{
	change_line(current_line+1);
}

void FileView::line_up()
{
	change_line( current_line - 1);

}

void FileView::delete_window()
{
	return ;
}

void FileView::page_up()
{
	int page = line_count -1 ;
	if (page < 1) page = 1 ;
	change_line( current_line - page) ;
}

void FileView::page_down()
{
	int page = line_count -1 ;
	if (page < 1) page = 1 ;
	change_line(current_line + page);
}

void FileView::begin()
{
	change_line(0);
}

void FileView::end()
{
	change_line(the_cache->last() - line_count );
}



void FileView::clear()
{
	GlyphIndex count = the_lines->b_count();
	// LogOut << "Gcount = " << count << "\n" ;
	for (GlyphIndex i = count -1; i > -1 ;i--) the_lines->b_remove(i);
	if (clear_flag) {
		clear_flag = 0 ;
		the_cache->mark_current_bad();
		the_cache->line_seek(0);
		last_not_terminated = 0 ;
	}
	current_line = -1 ;
}

void FileView::fill(int index)
{
	if (last_not_terminated) clear_flag = 1 ;
	if (clear_flag) clear();
/*
 *	LogOut<< "FileView::fill(" << index << ") current_line = " << current_line
 *		<< "\n" ;
 */
	int prev_curr = current_line ;
	if ((index != current_line) ||  (current_line < 0)) {
		if (index < 0) index = 0 ;
		clear();
		the_cache->line_seek(index);
		current_line = index ;
	}
	fill();
	if (current_line != prev_curr) adjust_adjuster();
}

int FileView::fill()
{
	if (last_not_terminated) clear_flag = 1 ;
	if (clear_flag) clear();
	const buf_size = 512 ;
	char buf[buf_size];
	int did_update = 0 ;
	int count = 0 ;
	for (;;) {
		Coord y_req_curr =  the_lines->b_count() * y_size ;
/*
 *		LogOut << "curr = " << y_req_curr << ", to_add = " << y_size <<
 *			", h = " << the_lines->height() << " : " << count++ <<"\n";
 */
 
		if (y_req_curr + y_size >= the_lines->height())
			break ;

		if (!the_cache->fgets(buf,buf_size)) break ;
		if(!buf[0]) break ;
		last_not_terminated = buf[strlen(buf) -1]  != '\n' ; 
		// LogOut << "last_not_terminated = " << last_not_terminated << ".\n" ;
		DspApplication::remove_control(buf);
		
		Glyph * l = the_layout.hbox(new Label(buf,font,DspApplication::black()),
			the_layout.hglue());
		the_lines->b_append(l);
		did_update = 1 ;
	}
	if (did_update) {
		// adjust_adjuster();
		redraw();
	}
	// LogOut << "FileView::fill returning " << did_update << "\n" ;
	return did_update ;
}

void FileView::update_size(const char * str,int extra_lines)
{
	the_cache->update_size(str,extra_lines);
	adjust_adjuster();
	focus(this);
}

void FileView::update_size(int32 lines,int32 bytes)
{
/*
 *	LogOut << "FileView::update_size("  << lines << ", " << bytes <<
 *		"), not_terminated = " << last_not_terminated << "\n" ;
 */
	the_cache->update_size(lines,bytes);
	fill();
	adjust_adjuster();
}

Adjustable* FileView::adjustable()
{
	return &box ;
}

void FileView::keystroke(const Event& e)
{
	if (!the_menu_keyboard->accept_keystroke(e))
            DspActiveHandler::keystroke(e);
}



FileWindow::FileWindow(WidgetKit& k, const LayoutKit& l, Style &s,
	const char * file_name, const Font * font, const Color * color,TxtWindow *t,
		int is_deletable):
		ManagedWindow(the_view= new FileView(k,l,s,file_name,font,color,t))
{
	style(&s);
	the_view->window(this,0,is_deletable);
	map();
}

void FileWindow::update_size(const char * str, int extra_lines)
{
	the_view->update_size(str,extra_lines);
}

void FileWindow::update_size(int32 lines, int32 bytes)
{
	the_view->update_size(lines,bytes);
}

void FileWindow::resize()
{
	// LogOut << "FileWindow::resize\n" ;
	the_view->resize();
	ManagedWindow::resize();
}

static void prompt_value_callback(const char * name, InputType, void* obj)
{
    ((FileView *)obj)->prompt_value(name);
}

void FileView::prompt_value(const char * val)
{
    got_value = 0 ;
    if (!val) return ;
    if (!*val) return ;
    if (*val == '\03') return ;
    {
        prompt_value_return = .3333e-33 ;
        istrstream value(val);
        value >> prompt_value_return ;
        if (!value.fail()) got_value = 1 ;
/*
 * 		LogOut << "prompt_value_return = " << prompt_value_return << ", "
 *			<< got_value << "\n" ;
 */
    }
}

int FileView::prompt_for_value(const char * msg)
{
     DspApplication::prompt(msg, prompt_value_callback,
        "",this,window());
    return got_value ;
}



void FileView::change_line(int32 new_line)
{
	int last_page = the_cache->last() - line_count ;
    if (new_line < 0) new_line = 0 ;
	if (new_line > last_page) new_line = last_page ;
    if (new_line == current_line) return ;
    fill(new_line);
    adjust_adjuster();
}

void FileView::to_line()
{
	if (!prompt_for_value("Specify line number to display:")) return;
    if (prompt_value_return > INT_MAX || prompt_value_return < INT_MIN) return;
	change_line((int)prompt_value_return);
}

void FileView::ahead_lines()
{
	if (!prompt_for_value(
        "Specify lines to move ahead (- to move back):")) return;
    if (prompt_value_return > INT_MAX || prompt_value_return < INT_MIN) return;
	change_line((int)(current_line + prompt_value_return)) ;
}

static void file_save_callback(const char * name, InputType, void* obj)
{
    ((FileView *)obj)->save_file(name);
}


void FileView::save_file(const char * name)
{
    if (!name) return ;
    if (!*name) return ;
    if (*name == '\03') return ;
	char * err = 0 ;
	FILE * input = fopen(the_cache->file_name(),"r");
	if (!input) err =
			Concatenate("Cannot open `",the_cache->file_name(), "'.\n");
	else {
		FILE * output = fopen(name,"w");
		if (!output) err = Concatenate("Cannot create file `", name , "'.\n");
		else {
			const buf_size = 4096;
			char buf[buf_size];
			for (;;) {
				int read = fread(buf,sizeof(char),buf_size,input);
				if (read < 1) break ;
				int write = fwrite(buf,sizeof(char),read,output);
				if (write !=read) {
					err = Concatenate("Error in writing to `", name, "'.\n");
					break ;
				}
			}
			fclose(input);
			fwrite("\n",sizeof(char),1,output);
			fclose(output);
		}
	}
	if (err) {
		StringList list ;
		list.Append(err);
		char * sys_err = 0 ;
		SystemErrorMessage(0,&sys_err);
		list.Append(sys_err);
		DspApplication::information(list,window());
	}
}

void FileView::save_file()
{
	osString s;
	const char * def = "file_win" ;
	if (the_style.find_attribute("name",s)) def = s.string();
    char * name = Concatenate(def,".txt");
	
	for (char * pt = name; *pt ; pt++) if (!isgraph(*pt)) *pt = '_';
	
	char * pr = Concatenate("Specify file to save text in `",def,"' to:");
    DspApplication::prompt(pr, file_save_callback,name,this,window());
    delete name ;
	delete pr ;
}



