/*
 * File    : TextTarget.java
 * Created : 31-may-2001 16:55
 * By      : fbusquets
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 (see the LICENSE file).
 */

package edu.xtec.jclic.activities.text;

import edu.xtec.jclic.bags.MediaBag;
import edu.xtec.jclic.boxes.*;
import edu.xtec.jclic.media.MediaContent;
import edu.xtec.util.JDomUtility;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

/**
 * @author Francesc Busquets (fbusquets@xtec.cat)
 * @version 13.09.10
 */
public class TextTarget extends java.lang.Object {

  public static final int NO_INFO = 0, INFO_ALWAYS = 1, INFO_ON_ERROR = 2, INFO_ON_DEMAND = 3;
  public static final String[] infoTypes = { "no_info", "always", "onError", "onDemand" };
  public static final int NOT_EDITED = 0, EDITED = 1, SOLVED = 2, WITH_ERROR = 3;
  public static final int INFO_LEFTALIGN = 1, INFO_ONLYPLAY = 2;

  public boolean isList;
  public int numIniChars;
  public char iniChar;
  public int maxLenResp;
  public String[] answer;
  public String[] options;
  public String iniText;
  public int infoMode;
  public ActiveBoxContent popupContent;
  public int popupDelay;
  public int popupMaxTime;
  public boolean onlyPlay;

  // TRANSIENT PROPERTIES
  protected TargetCombo comboList;
  public int targetStatus;
  private boolean flagModified;
  TextActivityPane parentPane;

  /** Creates new TextTarget */
  public TextTarget() {
    isList = false;
    numIniChars = 1;
    iniChar = '_';
    maxLenResp = 0;
    answer = null;
    options = null;
    iniText = null;
    infoMode = NO_INFO;
    popupContent = null;
    popupDelay = 0;
    popupMaxTime = 0;
    onlyPlay = false;
    comboList = null;
    targetStatus = NOT_EDITED;
    flagModified = false;
  }

  public TextTarget(Document doc, int x0, int x1) {
    this();
    if (x1 < x0) {
      int v = x0;
      x0 = x1;
      x1 = v;
    }
    String s;
    try {
      s = doc.getText(x0, x1 - x0);
    } catch (BadLocationException ex) {
      // should not occur!
      s = "";
    }
    numIniChars = s.length();
    answer = new String[] { s };
    maxLenResp = numIniChars;
  }

  public void reset() {
    targetStatus = NOT_EDITED;
    flagModified = false;
    if (comboList != null)
      comboList.checkColors();
  }

  public static final String ELEMENT_NAME = "target";
  public static final String ANSWER = "answer", OPTION_LIST = "optionList", OPTION = "option", RESPONSE = "response",
      FILL = "fill", INI_LEN = "length", MAX_LEN = "maxLength", INI_TEXT = "show", INFO = "info", MODE = "mode",
      DELAY = "delay", MAX_TIME = "maxTime";

  public org.jdom.Element getJDomElement() {

    org.jdom.Element e = new org.jdom.Element(ELEMENT_NAME);
    org.jdom.Element child;
    if (answer != null) {
      for (String ans : answer)
        e.addContent((new org.jdom.Element(ANSWER)).addContent(ans));
    }
    if (isList) {
      child = new org.jdom.Element(OPTION_LIST);
      if (options != null) {
        for (String opt : options)
          child.addContent((new org.jdom.Element(OPTION)).addContent(opt));
      }
      e.addContent(child);
    } else {
      child = new org.jdom.Element(RESPONSE);
      child.setAttribute(FILL, new String(new char[] { iniChar }));
      child.setAttribute(INI_LEN, Integer.toString(numIniChars));
      child.setAttribute(MAX_LEN, Integer.toString(maxLenResp));
      if (iniText != null && iniText.length() > 0)
        child.setAttribute(INI_TEXT, iniText);
      e.addContent(child);
    }

    if (popupContent != null && infoMode != NO_INFO) {
      child = new org.jdom.Element(INFO);
      if (infoMode != INFO_ALWAYS)
        child.setAttribute(MODE, infoTypes[infoMode]);
      if (popupDelay > 0)
        child.setAttribute(DELAY, Integer.toString(popupDelay));
      if (popupMaxTime > 0)
        child.setAttribute(MAX_TIME, Integer.toString(popupMaxTime));
      if (onlyPlay && popupContent.mediaContent != null) {
        child.addContent(popupContent.mediaContent.getJDomElement());
      } else {
        child.addContent(popupContent.getJDomElement());
      }
      e.addContent(child);
    }
    return e;
  }

  public static TextTarget getTextTarget(org.jdom.Element e, MediaBag mediaBag) throws Exception {
    TextTarget tt = new TextTarget();
    tt.setProperties(e, mediaBag);
    return tt;
  }

  public void setProperties(org.jdom.Element e, Object aux) throws Exception {
    JDomUtility.checkName(e, ELEMENT_NAME);
    MediaBag mediaBag = (MediaBag) aux;
    org.jdom.Element child, child2;

    java.util.List answerLst = e.getChildren(ANSWER);
    if (!answerLst.isEmpty()) {
      answer = new String[answerLst.size()];
      for (int i = 0; i < answer.length; i++)
        answer[i] = ((org.jdom.Element) (answerLst.get(i))).getText();
    }
    if ((child = e.getChild(OPTION_LIST)) != null) {
      java.util.List optionLst = child.getChildren(OPTION);
      if (!optionLst.isEmpty()) {
        isList = true;
        options = new String[optionLst.size()];
        for (int i = 0; i < options.length; i++)
          options[i] = ((org.jdom.Element) (optionLst.get(i))).getText();
      }
    }
    if ((child = e.getChild(RESPONSE)) != null) {
      String s = new String(new char[] { iniChar });
      s = JDomUtility.getStringAttr(child, FILL, s, true);
      if (s.length() > 0)
        iniChar = s.charAt(0);
      numIniChars = JDomUtility.getIntAttr(child, INI_LEN, numIniChars);
      maxLenResp = JDomUtility.getIntAttr(child, MAX_LEN, maxLenResp);
      iniText = child.getAttributeValue(INI_TEXT);
    }
    if ((child = e.getChild(INFO)) != null) {
      infoMode = JDomUtility.getStrIndexAttr(child, MODE, infoTypes, INFO_ALWAYS);
      popupDelay = JDomUtility.getIntAttr(child, DELAY, popupDelay);
      popupMaxTime = JDomUtility.getIntAttr(child, MAX_TIME, popupMaxTime);
      if ((child2 = child.getChild(MediaContent.ELEMENT_NAME)) != null) {
        onlyPlay = true;
        popupContent = new ActiveBoxContent();
        popupContent.mediaContent = MediaContent.getMediaContent(child2);
      } else if ((child2 = child.getChild(ActiveBoxContent.ELEMENT_NAME)) != null) {
        popupContent = ActiveBoxContent.getActiveBoxContent(child2, mediaBag);
      }
    }
  }

  protected void setParentPane(TextActivityPane pane) {
    parentPane = pane;
  }

  public void setAnswer(String text) {
    java.util.StringTokenizer st = new java.util.StringTokenizer(text, "|");
    int numItems = st.countTokens();
    if (numItems <= 0) {
      answer = null;
    } else {
      answer = new String[numItems];
      for (int i = 0; i < numItems; i++) {
        answer[i] = st.nextToken();
      }
    }
  }

  public String getAnswers() {
    StringBuilder sb = new StringBuilder();
    if (answer != null)
      for (int i = 0; i < answer.length; i++) {
        if (i > 0)
          sb.append("|");
        sb.append(answer[i]);
      }
    return sb.substring(0);
  }

  public boolean checkText(String txt, Evaluator ev) {
    if (answer == null || txt == null)
      return false;
    boolean ok = ev.checkText(txt, answer);
    targetStatus = ok ? SOLVED : WITH_ERROR;
    return ok;
  }

  public JComboBox buildCombo(AttributeSet targetAttr, AttributeSet errorAttr) {
    if (!isList || options == null)
      return null;
    comboList = new TargetCombo(options, targetAttr, errorAttr);
    if (iniText != null)
      comboList.setSelectedItem(iniText);
    return comboList;
  }

  public String getFillString() {
    return getFillString(numIniChars);
  }

  public String getFillString(int length) {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < length; i++) {
      s.append(iniChar);
    }
    return s.substring(0);
  }

  public void requestFocus(TextActivityBase.Panel tabp, TargetMarker tm) {
    if (comboList != null) {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          comboList.requestFocus();
        }
      });
    } else
      tabp.requestFocus();
    if (popupContent != null && (infoMode == INFO_ALWAYS || (infoMode == INFO_ON_ERROR && targetStatus == WITH_ERROR)))
      tabp.popupTimer.setUp(this, tm);
  }

  public void checkPopup(TextActivityBase.Panel tabp, TargetMarker tm, boolean f1Key) {
    if (popupContent != null) {
      boolean show = false;
      if (infoMode == INFO_ON_ERROR)
        show = (targetStatus == WITH_ERROR);
      else if (infoMode == INFO_ON_DEMAND)
        show = f1Key;

      if (show)
        tabp.popupTimer.setUp(this, tm);
      else
        tabp.popupTimer.stopPopup();
    }
  }

  protected Point getPopupLocation(TextActivityBase.Panel tabp, TargetMarker tm) {
    if (popupContent == null || onlyPlay)
      return null;

    Rectangle r = tm.getBegRect(tabp.pane);
    if (r == null)
      return null;

    Point pt = r.getLocation();
    pt.y -= popupContent.dimension.height;
    if (pt.y < 0)
      pt.y = r.y + r.height;

    if (pt.x + popupContent.dimension.width > tabp.pane.getWidth()) {
      pt.x -= (tabp.pane.getWidth() - pt.x + popupContent.dimension.width);
      if (pt.x < 0)
        pt.x = 0;
    }
    return pt;
  }

  public void adjustPopupLocation(TextActivityBase.Panel tabp, TargetMarker tm) {
    Point pt = getPopupLocation(tabp, tm);
    if (pt != null)
      tabp.pane.bx.setLocation(pt);
  }

  public void lostFocus(TextActivityBase.Panel tabp, TargetMarker tm) {
    if (comboList != null && parentPane != null) {
      parentPane.repaint();
    }
    if (popupContent != null)
      tabp.popupTimer.stopPopup();
  }

  public class TargetCombo extends JComboBox<String> {

    Color bgColor, foreColor;
    Color errBgColor, errForeColor;
    Font rFont;
    TargetComboCellRenderer cellRenderer;
    JTextField txEditor;
    AbstractAction showListAction, fwdAction, bkAction;

    public TargetCombo(String[] items, AttributeSet targetAttr, AttributeSet errorAttr) {
      super(items);
      setSelectedIndex(-1);

      showListAction = new AbstractAction("showList") {
        public void actionPerformed(ActionEvent e) {
          setPopupVisible(true);
        }
      };

      fwdAction = new AbstractAction("forward") {
        public void actionPerformed(ActionEvent e) {
          if (parentPane != null) {
            Action act = parentPane.getActionMap().get("next-target");
            if (act != null)
              act.actionPerformed(e);
          }
        }
      };

      bkAction = new AbstractAction("backwards") {
        public void actionPerformed(ActionEvent e) {
          if (parentPane != null) {
            Action act = parentPane.getActionMap().get("prev-target");
            if (act != null)
              act.actionPerformed(e);
          }
        }
      };

      rFont = TextActivityDocument.attributesToFont(targetAttr);
      setFont(rFont);
      bgColor = StyleConstants.getBackground(targetAttr);
      foreColor = StyleConstants.getForeground(targetAttr);
      errBgColor = StyleConstants.getBackground(errorAttr);
      errForeColor = StyleConstants.getForeground(errorAttr);
      setCursor(Cursor.getDefaultCursor());
      cellRenderer = new edu.xtec.jclic.activities.text.TextTarget.TargetCombo.TargetComboCellRenderer();
      setRenderer(cellRenderer);

      Dimension d = getPreferredSize();
      setPreferredSize(new Dimension((int) d.getWidth() + rFont.getSize() / 2, (int) d.getHeight()));

      setMaximumSize(getPreferredSize());
      setEditable(true);
      Component c = getEditor().getEditorComponent();
      if (c instanceof JTextField)
        txEditor = (JTextField) c;
      else
        txEditor = new JTextField();
      txEditor.setEnabled(false);
      checkColors();
      setAlignmentY(0.8f);
      setKActions();
    }

    protected void setKActions() {
      registerKeyboardAction(showListAction, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);
      registerKeyboardAction(showListAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
      txEditor.registerKeyboardAction(showListAction, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
          JComponent.WHEN_FOCUSED);
      txEditor.registerKeyboardAction(showListAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
          JComponent.WHEN_FOCUSED);

      registerKeyboardAction(fwdAction, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
      registerKeyboardAction(fwdAction, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0),
          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
      registerKeyboardAction(fwdAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
      registerKeyboardAction(bkAction, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
      registerKeyboardAction(bkAction, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, Event.SHIFT_MASK),
          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    public void checkColors() {
      if (targetStatus == WITH_ERROR) {
        txEditor.setBackground(errBgColor);
        txEditor.setForeground(errForeColor);
        txEditor.setDisabledTextColor(errForeColor);
      } else {
        txEditor.setBackground(bgColor);
        txEditor.setForeground(foreColor);
        txEditor.setDisabledTextColor(foreColor);
      }
    }

    class TargetComboCellRenderer extends JLabel implements ListCellRenderer<Object> {

      public TargetComboCellRenderer() {
        setOpaque(true);
        setFont(rFont);
      }

      public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
          boolean cellHasFocus) {
        setBackground(isSelected ? foreColor : bgColor);
        setForeground(isSelected ? bgColor : foreColor);
        setText(value == null ? "" : value.toString());
        return this;
      }
    }

    @Override
    public void setSelectedItem(Object o) {
      super.setSelectedItem(o);
      if (o != null && parentPane != null) {
        setModified(true);
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            if (!isPopupVisible()) {
              parentPane.targetChanged(TextTarget.this);
            }
          }
        });
      }
    }
  }

  public void setModified(boolean value) {
    flagModified = value;
    if (value == true && targetStatus == NOT_EDITED)
      targetStatus = EDITED;
  }

  public boolean isModified() {
    return flagModified;
  }

  public static class PopupTimer extends javax.swing.Timer {

    TextTarget target;
    boolean action;
    TextActivityBase.Panel tabp;
    TargetMarker tm;

    public PopupTimer(TextActivityBase.Panel tabp) {
      super(1000, null);
      this.tabp = tabp;
      setRepeats(false);
      target = null;
    }

    public void setUp(TextTarget setTarget, TargetMarker setTm) {
      stop();
      target = setTarget;
      tm = setTm;
      action = true;
      if (target != null && target.popupContent != null) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            if (target.popupDelay > 0) {
              setInitialDelay(target.popupDelay * 1000);
              setRepeats(false);
              start();
            } else
              startPopup();
          }
        });
      }
    }

    public void startPopup() {
      stop();
      if (target != null && target.popupContent != null) {
        if (!target.onlyPlay) {
          Point pt = target.getPopupLocation(tabp, tm);
          if (pt != null)
            tabp.pane.enableActiveBox(target.popupContent, pt);
        }
        if (target.popupContent.mediaContent != null)
          tabp.ps.playMedia(target.popupContent.mediaContent, tabp.pane.bx);
        if (target.popupMaxTime > 0) {
          action = false;
          setInitialDelay(target.popupMaxTime * 1000);
          setRepeats(false);
          start();
        }
      }
    }

    public void stopPopup() {
      stop();
      if (target != null && target.popupContent != null) {
        tabp.ps.stopMedia(1);
        tabp.pane.disableActiveBox();
      }
    }

    @Override
    protected void fireActionPerformed(java.awt.event.ActionEvent e) {
      if (action == true)
        startPopup();
      else
        stopPopup();
    }
  }
}
