/*
 * 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.widgets;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.form.api.FormEntryPrompt;
import org.odk.collect.android.R;
import org.odk.collect.android.views.MediaLayout;
import org.odk.collect.android.views.SuperscriptSpanAdjuster;

import java.util.ArrayList;
import java.util.List;

public abstract class QuestionWidget extends LinearLayout {

  @SuppressWarnings("unused")
  private final static String t = "QuestionWidget";
  private static final String APPEARENCE_HTML = "html";

  private static int idGenerator = 1211322;

  /**
   * Generate a unique ID to keep Android UI happy when the screen orientation changes.
   *
   * @return a unique ID to keep Android UI happy when the screen orientation changes.
   */
  public static int newUniqueId() {
    return ++idGenerator;
  }

  private LinearLayout.LayoutParams mLayout;
  /**
   * Contains data about the form entry.
   */
  protected FormEntryPrompt mPrompt;

  /**
   * Font size for questions/
   */
  protected final int mQuestionFontSize;
  /**
   * Font size for answers.
   */
  protected final int mAnswerFontSize;

  private TextView mQuestionText;
  private MediaLayout mediaLayout;
  private TextView mHelpText;


  /**
   * @param context
   *     the Context in which the View will be inflated.
   * @param p
   *     the entry prompt for this question.
   */
  public QuestionWidget(Context context, FormEntryPrompt p) {
    super(context);

    final Resources resources = getResources();
    mQuestionFontSize = resources.getDimensionPixelSize(R.dimen.question_text);
    mAnswerFontSize = resources.getDimensionPixelSize(R.dimen.answer_text);

    mPrompt = p;

    setOrientation(LinearLayout.VERTICAL);
    setGravity(Gravity.TOP);
    final int bottomPadding = resources.getDimensionPixelSize(R.dimen.question_padding_bottom);
    setPadding(0, 0, 0, bottomPadding);
    mLayout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

    if (showQuestion()) {
      addQuestionText(p);
    }
    if (showHelp()) {
      addHelpText(p);
    }
  }

  /**
   * @return {@code true} if the question text should be displayed, {@code false} if it should not.
   */
  boolean showQuestion() {
    return true;
  }

  /**
   * @return {@code true} if the help text should be displayed, {@code false} if it should not.
   */
  boolean showHelp() {
    return true;
  }

  /**
   * Play the current audio.
   */
  public void playAudio() {
    mediaLayout.playAudio();
  }

  /**
   * Play the current video.
   */
  public void playVideo() {
    mediaLayout.playVideo();
  }

  /**
   * @return the current prompt.
   */
  public FormEntryPrompt getPrompt() {
    return mPrompt;
  }

  private void recycleDrawablesRecursive(ViewGroup viewGroup, List<ImageView> images) {

    int childCount = viewGroup.getChildCount();
    for (int index = 0; index < childCount; index++) {
      View child = viewGroup.getChildAt(index);
      if (child instanceof ImageView) {
        images.add((ImageView) child);
      } else if (child instanceof ViewGroup) {
        recycleDrawablesRecursive((ViewGroup) child, images);
      }
    }
    viewGroup.destroyDrawingCache();
  }

  /**
   * Recycle the used Drawables.
   *
   * @see <a href="http://code.google.com/p/android/issues/detail?id=8488">Issue 8488:	'bitmap size exceeds VM budget'
   * if Activity is restarted [includes test demo!]</a>
   */
  public void recycleDrawables() {
    List<ImageView> images = new ArrayList<>();
    // collect all the image views
    recycleDrawablesRecursive(this, images);
    for (ImageView imageView : images) {
      imageView.destroyDrawingCache();
      Drawable d = imageView.getDrawable();
      if (d != null && d instanceof BitmapDrawable) {
        imageView.setImageDrawable(null);
        BitmapDrawable bd = (BitmapDrawable) d;
        Bitmap bmp = bd.getBitmap();
        if (bmp != null) {
          bmp.recycle();
        }
      }
    }
  }

  /**
   * @return the answer for this question.
   */
  public abstract IAnswerData getAnswer();


  /**
   * Clear the answer for this question.
   */
  public abstract void clearAnswer();

  /**
   * Set the focus for this View.
   *
   * @param context
   *     the current Context.
   */
  public abstract void setFocus(Context context);

  /**
   * @param l
   *     the long click listener to attach to thisquestion.
   */
  public abstract void setOnLongClickListener(OnLongClickListener l);

  /**
   * Override this to implement fling gesture suppression (e.g. for embedded WebView treatments).
   *
   * @param e1
   *     the motion event.
   * @param e2
   * @param velocityX
   *     the velocity on the X axis.
   * @param velocityY
   *     the velocity on the Y axis.
   *
   * @return true if the fling gesture should be suppressed
   */
  public boolean suppressFlingGesture(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    return false;
  }

  /**
   * Add a Views containing the question text, audio (if applicable), and image (if applicable). To satisfy the
   * RelativeLayout constraints, we add the audio first if it exists, then the TextView to fit the rest of the space,
   * then the image if applicable.
   *
   * @param p
   *     the form entry prompt for this question.
   */
  protected void addQuestionText(FormEntryPrompt p) {
    String imageURI = p.getImageText();
    String audioURI = p.getAudioText();
    String videoURI = p.getSpecialFormQuestionText("video");

    // shown when image is clicked
    String bigImageURI = p.getSpecialFormQuestionText("big-image");

    String promptText = p.getLongText();
    if (promptText == null) {
      promptText = "";
    }
    // Add the text view. TextView always exists, regardless of whether there's text.
    final Context context = getContext();
    final SpannableStringBuilder labelTextBuilder = new SpannableStringBuilder();
    final String appearanceHint = p.getAppearanceHint();
    final boolean hasAppearanceHint = appearanceHint != null && appearanceHint.trim().length() > 0;
    final String requiredIndicator = context.getString(R.string.question_mandatory_indicator);
    if (p.isRequired()) {
      promptText = context.getString(R.string.question_mandatory_label, promptText, requiredIndicator);
    }
    final boolean isHTML = hasAppearanceHint && appearanceHint.equalsIgnoreCase(APPEARENCE_HTML);
    mQuestionText = new TextView(context);

    if (isHTML) {
      labelTextBuilder.append(Html.fromHtml(promptText));
    } else {
      labelTextBuilder.append(promptText);
    }
    if (p.isRequired()) {
      final int position = labelTextBuilder.toString().indexOf(requiredIndicator);
      final int length = requiredIndicator.length();
      final int end = position + length;
      labelTextBuilder.setSpan(new SuperscriptSpanAdjuster(), position, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    final float textSize = getResources().getDimension(R.dimen.question_text);
    mQuestionText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
    mQuestionText.setTextColor(ContextCompat.getColor(context, R.color.question_text));
    mQuestionText.setTypeface(null, Typeface.NORMAL);
    mQuestionText.setText(promptText == null ? "" : labelTextBuilder);
    mQuestionText.setPadding(0, 0, 0, 0);
    mQuestionText.setId(QuestionWidget.newUniqueId()); // assign random id

    // Wrap to the size of the parent view
    mQuestionText.setHorizontallyScrolling(false);

    if (promptText == null || promptText.length() == 0) {
      this.mQuestionText.setVisibility(GONE);
    }

    // Create the layout for audio, image, text
    mediaLayout = new MediaLayout(context);
    mediaLayout.setAVT(p.getIndex(), "", mQuestionText, audioURI, imageURI, videoURI, bigImageURI);

    addView(mediaLayout, mLayout);
  }


  /**
   * Add a TextView containing the help text.
   *
   * @param p
   *     the FormEntryPrompt for this View.
   */

  void addHelpText(FormEntryPrompt p) {

    String s = p.getHelpText();

    if (s != null && !s.trim().equals("")) {
      final Context context = getContext();
      mHelpText = new TextView(context);
      mHelpText.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.help_text));
      mHelpText.setPadding(0, 0, 0, 0);
      // wrap to the widget of view
      mHelpText.setHorizontallyScrolling(false);
      mHelpText.setText(s);
      mHelpText.setTextColor(ContextCompat.getColor(context, R.color.hint_text));
      mHelpText.setMovementMethod(LinkMovementMethod.getInstance());

      mHelpText.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
          onHelpClick();
        }
      });

      final LayoutParams helpParams = new LayoutParams(LayoutParams.MATCH_PARENT,
          LinearLayout.LayoutParams.WRAP_CONTENT);
      helpParams.setMarginStart(getResources().getDimensionPixelSize(R.dimen.question_padding_start));
      addView(mHelpText, helpParams);
    }
  }

  /**
   * Called when the help text is tapped.
   */
  void onHelpClick() {
    // Can be overridden by subclasses.
  }

  /**
   * Every subclassed widget should override this, adding any views they may contain, and calling
   * super.cancelLongPress()
   */
  public void cancelLongPress() {
    super.cancelLongPress();
    if (mQuestionText != null) {
      mQuestionText.cancelLongPress();
    }
    if (mHelpText != null) {
      mHelpText.cancelLongPress();
    }
  }
}
