/*
 * Copyright (c) 1999 Vectaport Inc.
 * Copyright (c) 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the names of the copyright holders not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  The copyright holders make
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * FieldEditor -- simple editor for text fields
 */

#include <Dispatch/dispatcher.h>
#include <Dispatch/iocallback.h>
#include <IV-look/field.h>
#include <IV-look/kit.h>
#include <InterViews/background.h>
#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/font.h>
#include <InterViews/event.h>
#include <InterViews/hit.h>
#include <InterViews/layout.h>
#include <InterViews/printer.h>
#include <InterViews/selection.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <IV-2_6/InterViews/button.h>
#include <IV-2_6/InterViews/painter.h>
#include <IV-2_6/InterViews/sensor.h>
#include <IV-2_6/InterViews/streditor.h>
#include <IV-2_6/InterViews/textdisplay.h>
#include <IV-2_6/InterViews/world.h>

#include <IV-X11/Xlib.h>
#include <IV-X11/xdisplay.h>
#include <IV-X11/xevent.h>
#include <IV-X11/xwindow.h>
#include <IV-X11/xcanvas.h>
#include <InterViews/window.h>
extern "C" {
#include <X11/Xatom.h>
}

#include <OS/math.h>
#include <OS/string.h>
#include <cstdio>
#include <string.h>

class FieldStringEditor : public StringEditor {
public:
    FieldStringEditor(
	ButtonState* bs, const char* sample, WidgetKit*, Style*
    );
    virtual ~FieldStringEditor();

    virtual void print(Printer*, const Allocation&) const;
    virtual void pick(Canvas*, const Allocation&, int depth, Hit&);

    void press(const Event&);
    boolean keystroke(const Event&);
    void cursor_on();
    void cursor_off();
    void focus_in();
    void focus_out();
    void cut(SelectionManager*);
    void paste(SelectionManager*);
protected:
    virtual void Reconfig();
private:
    WidgetKit* kit_;
    Style* style_;
    int start_;
    int index_;

    void do_select(Event&);
    void do_grab_scroll(Event&);
    void do_rate_scroll(Event&);
    void do_xselection_request(Event&);
public:
    void do_xselection_paste(const Event&);
};

declareSelectionCallback(FieldStringEditor)
implementSelectionCallback(FieldStringEditor)

FieldStringEditor::FieldStringEditor(
    ButtonState* bs, const char* sample, WidgetKit* kit, Style* style
) : StringEditor(bs, sample) {
    kit_ = kit;
    style_ = style;
    Resource::ref(style);
    delete input_;
    input_ = nil;
}

FieldStringEditor::~FieldStringEditor() {
    Resource::unref(style_);
}

void FieldStringEditor::print(Printer* p, const Allocation& a) const {
    const Font* f = output_->GetFont();
    const Color* fg = output_->GetFgColor();
    FontBoundingBox b;
    f->font_bbox(b);
    Coord x = a.left(), y = a.bottom() + b.font_descent();
    FieldStringEditor* e = (FieldStringEditor*)this;
    for (const char* s = e->Text(); *s != '\0'; s++) {
	Coord w = f->width(*s);
	p->character(f, *s, w, fg, x, y);
	x += w;
    }
}

void FieldStringEditor::pick(
    Canvas*, const Allocation& a, int depth, Hit& h
) {
    const Event* ep = h.event();
    if (ep != nil && h.left() < a.right() && h.right() >= a.left() &&
	h.bottom() < a.top() && h.top() >= a.bottom()
    ) {
	h.target(depth, this, 0);
    }
}

void FieldStringEditor::press(const Event& event) {
    Event e;
    display->Draw(output_, canvas);
    switch (event.pointer_button()) {
    case Event::left:
	do_select(e);
	break;
    case Event::middle:
#if 0
        do_grab_scroll(e);
#else
	do_xselection_request(e);
#endif
	break;
    case Event::right:
	do_rate_scroll(e);
	break;
    }
}

void FieldStringEditor::do_select(Event& e) {
    int origin = display->Left(0, 0);
    int width = display->Width();
    Poll(e);
    start_ = display->LineIndex(0, e.x);
    do {
	if (e.x < 0) {
	    origin = Math::min(0, origin - e.x);
	} else if (e.x > xmax) {
	    origin = Math::max(
		xmax - width, origin - (e.x - xmax)
	    );
	}
	display->Scroll(0, origin, ymax);
	index_ = display->LineIndex(0, e.x);
	DoSelect(start_, index_);
	Poll(e);
    } while (e.leftmouse);
    SelectionManager* s = e.display()->primary_selection();
    s->own(
	new SelectionCallback(FieldStringEditor)(this, &FieldStringEditor::cut)
    );
}

void FieldStringEditor::do_grab_scroll(Event& e) {
    Window* w = canvas->window();
    Cursor* c = w->cursor();
    w->cursor(kit_->hand_cursor());
    int origin = display->Left(0, 0);
    int width = display->Width();
    Poll(e);
    int x = e.x;
    do {
	origin += e.x - x;
	origin = Math::min(
	    0, Math::max(Math::min(0, xmax - width), origin)
	);
	display->Scroll(0, origin, ymax);
	x = e.x;
	Poll(e);
    } while (e.middlemouse);
    w->cursor(c);
}

void FieldStringEditor::do_rate_scroll(Event& e) {
    Window* w = canvas->window();
    Cursor* c = w->cursor();
    WidgetKit& kit = *kit_;
    Cursor* left = kit.lfast_cursor();
    Cursor* right = kit.rfast_cursor();
    int origin = display->Left(0, 0);
    int width = display->Width();
    Poll(e);
    int x = e.x;
    do {
	origin += x - e.x;
	origin = Math::min(
	    0, Math::max(Math::min(0, xmax - width), origin)
	);
	display->Scroll(0, origin, ymax);
	if (e.x - x < 0) {
	    w->cursor(left);
	} else {
	    w->cursor(right);
	}
	Poll(e);
    } while (e.rightmouse);
    w->cursor(c);
}

void FieldStringEditor::do_xselection_request(Event& e) {
  int origin = display->Left(0, 0);
  int width = display->Width();
  Poll(e);
  start_ = display->LineIndex(0, e.x);
  if (e.x < 0) {
    origin = Math::min(0, origin - e.x);
  } else if (e.x > xmax) {
    origin = Math::max(
		       xmax - width, origin - (e.x - xmax)
		       );
  }
  display->Scroll(0, origin, ymax);
  index_ = display->LineIndex(0, e.x);
  DoSelect(start_, index_);
  XDisplay* disp = GetWorld()->display()->rep()->display_;
  unsigned int win = GetCanvas()->rep()->window_->rep()->xwindow_;
  Atom target_property = XInternAtom(disp, "PASTESTRING", false);
  int ret = XConvertSelection(disp, XA_PRIMARY, XA_STRING, target_property,
			      win, e.rep()->xevent_.xbutton.time);
}

void FieldStringEditor::do_xselection_paste(const Event& e) {
  XDisplay* disp = GetWorld()->display()->rep()->display_;
  unsigned int win = GetCanvas()->rep()->window_->rep()->xwindow_;
  Atom target_property = XInternAtom(disp, "PASTESTRING", false);
  Atom actual_atom;
  int actual_format;
  unsigned long ret_length, ret_remaining;
  unsigned char* data;
  int ret=XGetWindowProperty(disp, win, target_property,
			     0L, BUFSIZ, false, XA_STRING,
			     &actual_atom, &actual_format, &ret_length,
			     &ret_remaining, &data);
  if (ret == Success && data) {
    InsertText((char*) data, strlen((char*)data));
    XFree(data);
  }
}


boolean FieldStringEditor::keystroke(const Event& e) {
    char c;
    return e.mapkey(&c, 1) != 0 && HandleChar(c) && c == '\t';
}

void FieldStringEditor::cursor_on() {
    if (canvas != nil) {
	display->CaretStyle(BarCaret);
    }
}

void FieldStringEditor::cursor_off() {
    if (canvas != nil) {
	display->CaretStyle(NoCaret);
    }
}

void FieldStringEditor::focus_in() { }
void FieldStringEditor::focus_out() { }

void FieldStringEditor::cut(SelectionManager* s) {
    s->put_value(Text() + start_, index_ - start_);
}

void FieldStringEditor::paste(SelectionManager*) {
    /* unimplemented */
}

void FieldStringEditor::Reconfig() {
    kit_->push_style();
    kit_->style(style_);
    Painter* p = new Painter(output_);
    p->SetColors(kit_->foreground(), kit_->background());
    p->SetFont(kit_->font());
    Resource::unref(output_);
    output_ = p;
    StringEditor::Reconfig();
    kit_->pop_style();
}

class FieldButton : public ButtonState {
public:
    FieldButton(FieldEditor*, FieldEditorAction*);
    virtual ~FieldButton();

    virtual void Notify();
private:
    FieldEditor* editor_;
    FieldEditorAction* action_;
};

class FieldEditorImpl {
private:
    friend class FieldEditor;

    WidgetKit* kit_;
    FieldStringEditor* editor_;
    FieldButton* bs_;
    String text_;
    boolean cursor_is_on_;
    IOHandler* blink_handler_;
    long flash_rate_;

    void build(FieldEditor*, const char*, FieldEditorAction*);
    void blink_cursor(long, long);
    void stop_blinking();
};

declareIOCallback(FieldEditorImpl)
implementIOCallback(FieldEditorImpl)

FieldEditor::FieldEditor(
    const String& sample, WidgetKit* kit, Style* s, FieldEditorAction* action
) : InputHandler(nil, s) {
    impl_ = new FieldEditorImpl;
    impl_->kit_ = kit;
    NullTerminatedString ns(sample);
    impl_->build(this, ns.string(), action);
}

FieldEditor::~FieldEditor() {
    FieldEditorImpl* i = impl_;
    i->stop_blinking();
    Resource::unref(i->editor_);
    Resource::unref(i->bs_);
    delete i->blink_handler_;
    delete i;
}

void FieldEditor::undraw() {
    FieldEditorImpl& f = *impl_;
    f.stop_blinking();
    InputHandler::undraw();
}

void FieldEditor::press(const Event& e) {
    impl_->editor_->press(e);
}

void FieldEditor::selection_notify(const Event& e) {
  impl_->editor_->do_xselection_paste(e);
}

void FieldEditor::drag(const Event&) { }
void FieldEditor::release(const Event&) { }

void FieldEditor::keystroke(const Event& e) {
    FieldEditorImpl& f = *impl_;
    if (f.editor_->keystroke(e)) {
	select(text()->length());
	next_focus();
    }
}

InputHandler* FieldEditor::focus_in() {
    FieldEditorImpl& f = *impl_;
    f.blink_cursor(0, 0);
    f.editor_->focus_in();
    return InputHandler::focus_in();
}

void FieldEditor::focus_out() {
    FieldEditorImpl& f = *impl_;
    f.stop_blinking();
    f.editor_->cursor_off();
    f.editor_->focus_out();
    InputHandler::focus_out();
}

void FieldEditor::field(const char* str) {
    impl_->editor_->Message(str);
}

void FieldEditor::field(const String& s) {
    NullTerminatedString ns(s);
    impl_->editor_->Message(ns.string());
}

void FieldEditor::select(int pos) {
    impl_->editor_->Select(pos);
}

void FieldEditor::select(int l, int r) {
    impl_->editor_->Select(l, r);
}

void FieldEditor::edit() {
    impl_->editor_->Edit();
}

void FieldEditor::edit(const char* str, int left, int right) {
    impl_->editor_->Edit(str, left, right);
}

void FieldEditor::edit(const String& str, int left, int right) {
    NullTerminatedString ns(str);
    impl_->editor_->Edit(ns.string(), left, right);
}

const String* FieldEditor::text() const {
    impl_->text_ = String(impl_->editor_->Text());
    return &impl_->text_;
}

/** class FieldEditorImpl **/

void FieldEditorImpl::build(
    FieldEditor* e, const char* str, FieldEditorAction* a
) {
    WidgetKit& kit = *kit_;
    kit.begin_style("FieldEditor");
    Style* s  = kit.style();
    bs_ = new FieldButton(e, a);
    editor_ = new FieldStringEditor(bs_, str, kit_, s);
    Glyph* g = editor_;
    if (s->value_is_on("beveled")) {
	g = kit.inset_frame(
	    new Background(
		LayoutKit::instance()->h_margin(editor_, 2.0),
		kit.background()
	    )
	);
    }
    e->body(g);
    cursor_is_on_ = false;
    blink_handler_ = new IOCallback(FieldEditorImpl)(
	this, &FieldEditorImpl::blink_cursor
    );
    float sec = 0.5;
    s->find_attribute("cursorFlashRate", sec);
    flash_rate_ = long(sec * 1000000);
    kit.end_style();
}

void FieldEditorImpl::blink_cursor(long, long) {
    if (cursor_is_on_) {
	editor_->cursor_off();
	cursor_is_on_ = false;
    } else {
	editor_->cursor_on();
	cursor_is_on_ = true;
    }
    if (flash_rate_ > 10) {
	Dispatcher::instance().startTimer(0, flash_rate_, blink_handler_);
    }
}

void FieldEditorImpl::stop_blinking() {
    Dispatcher::instance().stopTimer(blink_handler_);
    editor_->cursor_off();
    cursor_is_on_ = false;
}

/** class FieldButton **/

FieldButton::FieldButton(FieldEditor* editor, FieldEditorAction* action) {
    editor_ = editor;
    action_ = action;
    Resource::ref(action_);
}

FieldButton::~FieldButton() {
    Resource::unref(action_);
}

/*
 * We need to reset the button state's value so that we'll get
 * notified again.  If we call SetValue, then it will call
 * Notify again!  Alas, we must access the protected value member (sigh).
 */

void FieldButton::Notify() {
    int v;
    GetValue(v);
    value = 0;
    if (action_ != nil) {
	switch (v) {
	case '\r':
	    action_->accept(editor_);
	    break;
	case /*  ^G */ '\007':
	case /* Esc */ '\033':
	    action_->cancel(editor_);
	    break;
	default:
	    break;
	}
    }
}

/** class FieldEditorAction **/

FieldEditorAction::FieldEditorAction() { }
FieldEditorAction::~FieldEditorAction() { }
void FieldEditorAction::accept(FieldEditor*) { }
void FieldEditorAction::cancel(FieldEditor*) { }
