/*
 * Copyright (C) 2011 University of Washington
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.odk.collect.android.views;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.javarosa.core.model.Constants;
import org.javarosa.core.model.FormIndex;
import org.javarosa.core.model.IFormElement;
import org.javarosa.core.model.QuestionDef;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.form.api.FormEntryCaption;
import org.javarosa.form.api.FormEntryPrompt;
import org.odk.collect.android.R;
import org.odk.collect.android.activities.FormEntryActivity;
import org.odk.collect.android.activities.PermissionActivity;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.exception.ExternalParamsException;
import org.odk.collect.android.exception.JavaRosaException;
import org.odk.collect.android.external.ExternalAppsUtils;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.widgets.IBinaryWidget;
import org.odk.collect.android.widgets.QuestionWidget;
import org.odk.collect.android.widgets.WidgetFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * This class is
 *
 * @author carlhartung
 */
public class ODKView extends ScrollView implements OnLongClickListener {

  /**
   * Broadcast for navigating to the previous page of a form.
   */
  public static final String BROADCAST_PREVIOUS_PAGE = "previousPage";

  /**
   * Broadcast for deleting a repeating group.
   */
  public static final String BROADCAST_DELETE_REPEATING_GROUP = "deleteRepeatingGroup";

  // starter random number for view IDs
  private final static int VIEW_ID = 12345;

  private final static String t = "ODKView";
  private final boolean readOnly;

  private LinearLayout mView;
  private LinearLayout.LayoutParams mLayout;
  private ArrayList<QuestionWidget> widgets;
  private Handler h = null;

  public final static String FIELD_LIST = "field-list";

  public ODKView(Context context, final FormEntryPrompt[] questionPrompts, FormEntryCaption[] groups,
                 boolean advancingPage) {
    this(context, questionPrompts, groups, advancingPage, false);
  }

  public ODKView(Context context, final FormEntryPrompt[] questionPrompts, FormEntryCaption[] groups,
                 boolean advancingPage, final boolean readOnly) {
    super(context);
    this.readOnly = readOnly;

    widgets = new ArrayList<>();

    mView = new LinearLayout(getContext());
    mView.setOrientation(LinearLayout.VERTICAL);
    mView.setGravity(Gravity.TOP);
    mView.setPadding(0, 7, 0, 0);

    mLayout = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
        LinearLayout.LayoutParams.WRAP_CONTENT);
    mLayout.setMargins(10, 0, 10, 0);

    // display which group you are in as well as the question

    addGroupText(groups);

    // when the grouped fields are populated by an external app, this will get true.
    boolean readOnlyOverride = false;

    // get the group we are showing -- it will be the last of the groups in the groups list
    if (groups != null && groups.length > 0) {
      final FormEntryCaption c = groups[groups.length - 1];
      final String intentString = c.getFormElement().getAdditionalAttribute(null, "intent");
      if (intentString != null && intentString.length() != 0) {

        readOnlyOverride = true;

        final String buttonText;
        final String errorString;
        String v = c.getSpecialFormQuestionText("buttonText");
        buttonText = (v != null) ? v : context.getString(R.string.launch_app);
        v = c.getSpecialFormQuestionText("noAppErrorString");
        errorString = (v != null) ? v : context.getString(R.string.no_app);

        TableLayout.LayoutParams params = new TableLayout.LayoutParams();
        params.setMargins(7, 5, 7, 5);

        // set button formatting
        Button mLaunchIntentButton = new Button(getContext());
        mLaunchIntentButton.setId(QuestionWidget.newUniqueId());
        mLaunchIntentButton.setText(buttonText);
        mLaunchIntentButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, Collect.getQuestionFontsize() + 2);
        mLaunchIntentButton.setPadding(20, 20, 20, 20);
        mLaunchIntentButton.setLayoutParams(params);

        mLaunchIntentButton.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            String intentName = ExternalAppsUtils.extractIntentName(intentString);
            Map<String, String> parameters = ExternalAppsUtils.extractParameters(intentString);

            Intent i = new Intent(intentName);
            try {
              ExternalAppsUtils.populateParameters(i, parameters, c.getIndex().getReference());

              for (FormEntryPrompt p : questionPrompts) {
                IFormElement formElement = p.getFormElement();
                if (formElement instanceof QuestionDef) {
                  TreeReference reference = (TreeReference) formElement.getBind().getReference();
                  IAnswerData answerValue = p.getAnswerValue();
                  Object value = answerValue == null ? null : answerValue.getValue();
                  switch (p.getDataType()) {
                    case Constants.DATATYPE_TEXT:
                    case Constants.DATATYPE_INTEGER:
                    case Constants.DATATYPE_DECIMAL:
                      i.putExtra(reference.getNameLast(), (Serializable) value);
                      break;
                  }
                }
              }

              ((Activity) getContext()).startActivityForResult(i, FormEntryActivity.EX_GROUP_CAPTURE);
            } catch (ExternalParamsException e) {
              Log.e("ExternalParamsException", e.getMessage(), e);

              Toast.makeText(getContext(),
                  e.getMessage(), Toast.LENGTH_SHORT)
                  .show();
            } catch (ActivityNotFoundException e) {
              Log.e("ActivityNotFound", e.getMessage(), e);

              Toast.makeText(getContext(),
                  errorString, Toast.LENGTH_SHORT)
                  .show();
            }
          }
        });

        mView.addView(mLaunchIntentButton, mLayout);
      }
    }

    int id = 0;
    for (FormEntryPrompt p : questionPrompts) {
      QuestionWidget qw = WidgetFactory.createWidgetFromPrompt(p, getContext(), readOnlyOverride);
      qw.setLongClickable(true);
      qw.setOnLongClickListener(this);
      qw.setId(VIEW_ID + id++);
      widgets.add(qw);
      mView.addView(qw, mLayout);
    }
    addView(mView);

    // see if there is an autoplay option.
    // Only execute it during forward swipes through the form
    if (advancingPage && widgets.size() == 1) {
      final String playOption = widgets.get(0).getPrompt().getFormElement().getAdditionalAttribute(null, "autoplay");
      if (playOption != null) {
        h = new Handler();
        h.postDelayed(new Runnable() {
          @Override
          public void run() {
            if (playOption.equalsIgnoreCase("audio")) {
              widgets.get(0).playAudio();
            } else if (playOption.equalsIgnoreCase("video")) {
              widgets.get(0).playVideo();
            }
          }
        }, 150);
      }
    }
  }

  /**
   * http://code.google.com/p/android/issues/detail?id=8488
   */
  public void recycleDrawables() {
    this.destroyDrawingCache();
    mView.destroyDrawingCache();
    for (QuestionWidget q : widgets) {
      q.recycleDrawables();
    }
  }

  protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    Collect.getInstance().getActivityLogger().logScrollAction(this, t - oldt);
  }

  /**
   * @return a HashMap of answers entered by the user for this set of widgets
   */
  public LinkedHashMap<FormIndex, IAnswerData> getAnswers() {
    LinkedHashMap<FormIndex, IAnswerData> answers = new LinkedHashMap<>();
    Iterator<QuestionWidget> i = widgets.iterator();
    while (i.hasNext()) {
            /*
             * The FormEntryPrompt has the FormIndex, which is where the answer gets stored. The
             * QuestionWidget has the answer the user has entered.
             */
      QuestionWidget q = i.next();
      FormEntryPrompt p = q.getPrompt();
      answers.put(p.getIndex(), q.getAnswer());
    }

    return answers;
  }


  /**
   * Add a TextView containing the hierarchy of groups to which the question belongs. //
   */
  private void addGroupText(FormEntryCaption[] groups) {
    StringBuilder s = new StringBuilder("");
    String t = "";
    int i;
    // list all groups in one string
    for (FormEntryCaption g : groups) {
      i = g.getMultiplicity() + 1;
      t = g.getLongText();
      if (t != null) {
        s.append(t);
        if (g.repeats() && i > 0) {
          s.append(" (").append(i).append(")");
        }
        s.append(" > ");
      }
    }

    // build view
    if (s.length() > 0) {
      final Context context = getContext();
      final View headerView = LayoutInflater.from(context).inflate(R.layout.group_header, mView, false);
      TextView tv = (TextView) headerView.findViewById(R.id.group_header_text);
      final View deleteButton = headerView.findViewById(R.id.group_header_delete);
      if (!readOnly && Collect.getInstance().getFormController().indexContainsRepeatableGroup()) {
        deleteButton.setOnClickListener(new OnClickListener() {
          @Override
          public void onClick(final View v) {
            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(BROADCAST_DELETE_REPEATING_GROUP));
          }
        });
        deleteButton.setVisibility(VISIBLE);
      } else {
        deleteButton.setVisibility(GONE);
      }
      tv.setText(s.substring(0, s.length() - 3));
      final Resources resources = getResources();
      tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.group_text));
      tv.setTextColor(ContextCompat.getColor(context, R.color.highlight));
      final int startPadding = resources.getDimensionPixelSize(R.dimen.question_padding_start);
      tv.setPadding(startPadding, 0, 0, 5);
      mView.addView(headerView, mLayout);
    }
  }

  /**
   * Creates a confirm/cancel dialog for deleting repeats.
   */
  private void createDeleteRepeatConfirmDialog() {
    Collect.getInstance().getActivityLogger().logInstanceAction(this, "createDeleteRepeatConfirmDialog", "show");
    final FormController formController = Collect.getInstance().getFormController();
    final Context context = getContext();
    final AlertDialog mAlertDialog = new AlertDialog.Builder(context).create();
    String name = formController.getLastRepeatedGroupName();
    final int repeatCount = formController.getLastRepeatedGroupRepeatCount();
    if (repeatCount != -1) {
      name += " (" + (repeatCount + 1) + ")";
    }
    mAlertDialog.setTitle(context.getString(R.string.delete_repeat_ask));
    mAlertDialog.setMessage(context.getString(R.string.delete_repeat_confirm, name));
    final DialogInterface.OnClickListener quitListener = new DialogInterface.OnClickListener() {
      @Override
      public void onClick(final DialogInterface dialog, final int i) {
        final FormController formController = Collect.getInstance().getFormController();
        switch (i) {
          case DialogInterface.BUTTON_POSITIVE: // yes
            Collect.getInstance().getActivityLogger().logInstanceAction(this, "createDeleteRepeatConfirmDialog", "OK");
            formController.deleteRepeat();
            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(BROADCAST_PREVIOUS_PAGE));
            break;
          case DialogInterface.BUTTON_NEGATIVE: // no
            Collect.getInstance().getActivityLogger().logInstanceAction(this, "createDeleteRepeatConfirmDialog",
                "cancel");
            break;
        }
      }
    };
    mAlertDialog.setCancelable(false);
    mAlertDialog.setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.discard_group), quitListener);
    mAlertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(R.string.delete_repeat_no), quitListener);
    mAlertDialog.show();
  }


  public void setFocus(Context context) {
    if (widgets.size() > 0) {
      widgets.get(0).setFocus(context);
    }
  }


  /**
   * Called when another activity returns information to answer this question.
   *
   * @param answer
   */
  public void setBinaryData(Object answer) {
    boolean set = false;
    for (QuestionWidget q : widgets) {
      if (q instanceof IBinaryWidget) {
        if (((IBinaryWidget) q).isWaitingForBinaryData()) {
          try {
            ((IBinaryWidget) q).setBinaryData(answer);
          } catch (Exception e) {
            Log.e(t, e.getMessage(), e);
            Toast.makeText(getContext(), getContext().getString(R.string.error_attaching_binary_file, e.getMessage())
                , Toast.LENGTH_LONG).show();
          }
          set = true;
          break;
        }
      }
    }

    if (!set) {
      Log.w(t, "Attempting to return data to a widget or set of widgets not looking for data");
    }
  }

  public void setDataForFields(Bundle bundle) throws JavaRosaException {
    if (bundle == null) {
      return;
    }
    FormController formController = Collect.getInstance().getFormController();
    Set<String> keys = bundle.keySet();
    for (String key : keys) {
      for (QuestionWidget questionWidget : widgets) {
        FormEntryPrompt prompt = questionWidget.getPrompt();
        TreeReference treeReference = (TreeReference) prompt.getFormElement().getBind().getReference();
        if (treeReference.getNameLast().equals(key)) {

          switch (prompt.getDataType()) {
            case Constants.DATATYPE_TEXT:
              formController.saveAnswer(prompt.getIndex(), ExternalAppsUtils.asStringData(bundle.get(key)));
              break;
            case Constants.DATATYPE_INTEGER:
              formController.saveAnswer(prompt.getIndex(), ExternalAppsUtils.asIntegerData(bundle.get(key)));
              break;
            case Constants.DATATYPE_DECIMAL:
              formController.saveAnswer(prompt.getIndex(), ExternalAppsUtils.asDecimalData(bundle.get(key)));
              break;
            default:
              throw new RuntimeException(getContext().getString(R.string.ext_assign_value_error, treeReference
                  .toString(false)));
          }

          break;
        }
      }
    }
  }

  public void cancelWaitingForBinaryData() {
    int count = 0;
    for (QuestionWidget q : widgets) {
      if (q instanceof IBinaryWidget) {
        if (((IBinaryWidget) q).isWaitingForBinaryData()) {
          ((IBinaryWidget) q).cancelWaitingForBinaryData();
          ++count;
        }
      }
    }

    if (count != 1) {
      Log.w(t, "Attempting to cancel waiting for binary data to a widget or set of widgets not looking for data");
    }
  }

  public boolean suppressFlingGesture(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    for (QuestionWidget q : widgets) {
      if (q.suppressFlingGesture(e1, e2, velocityX, velocityY)) {
        return true;
      }
    }
    return false;
  }

  /**
   * @return true if the answer was cleared, false otherwise.
   */
  public boolean clearAnswer() {
    // If there's only one widget, clear the answer.
    // If there are more, then force a long-press to clear the answer.
    if (widgets.size() == 1 && !widgets.get(0).getPrompt().isReadOnly()) {
      widgets.get(0).clearAnswer();
      return true;
    } else {
      return false;
    }
  }


  public ArrayList<QuestionWidget> getWidgets() {
    return widgets;
  }


  @Override
  public void setOnFocusChangeListener(OnFocusChangeListener l) {
    for (int i = 0; i < widgets.size(); i++) {
      QuestionWidget qw = widgets.get(i);
      qw.setOnFocusChangeListener(l);
    }
  }


  @Override
  public boolean onLongClick(View v) {
    return false;
  }


  @Override
  public void cancelLongPress() {
    super.cancelLongPress();
    for (QuestionWidget qw : widgets) {
      qw.cancelLongPress();
    }
  }

  /**
   * Called when the View receives a permission update.
   *
   * @param requestCode
   *     the code for the permission request.
   * @param results
   *     the results of the permission request.
   */
  public void onPermissionsResult(final int requestCode, final HashMap<String, Boolean> results) {
    for (QuestionWidget q : widgets) {
      if (q instanceof PermissionActivity.PermissionRequired && ((PermissionActivity.PermissionRequired) q)
          .isWaitingForPermission()) {
        ((PermissionActivity.PermissionRequired) q).onPermissionsResult(requestCode, results);
      }
    }
  }

  public void cancelWaitingForPermission() {
    for (QuestionWidget q : widgets) {
      if (q instanceof PermissionActivity.PermissionRequired) {
        if (((PermissionActivity.PermissionRequired) q).isWaitingForPermission()) {
          ((PermissionActivity.PermissionRequired) q).cancelWaitingForPermission();
        }
      }
    }
  }
}
