/*
 * Copyright (C) 2009 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.database.Cursor;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;

import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.data.StringData;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.form.api.FormEntryPrompt;
import org.javarosa.xpath.XPathNodeset;
import org.javarosa.xpath.XPathParseTool;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.parser.XPathSyntaxException;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.database.ItemsetDbAdapter;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * The most basic widget that allows for entry of any text.
 *
 * @author Carl Hartung (carlhartung@gmail.com)
 * @author Yaw Anokwa (yanokwa@gmail.com)
 */
public class ItemsetWidget extends QuestionWidget implements
    android.widget.CompoundButton.OnCheckedChangeListener {

  private static String tag = "ItemsetWidget";

  boolean mReadOnly;
  protected RadioGroup mButtons;
  private String mAnswer = null;
  // Hashmap linking label:value
  private HashMap<String, String> mAnswers;

  public ItemsetWidget(Context context, FormEntryPrompt prompt, boolean readOnlyOverride) {
    this(context, prompt, readOnlyOverride, true);
  }

  protected ItemsetWidget(Context context, FormEntryPrompt prompt, boolean readOnlyOverride,
                          boolean derived) {
    super(context, prompt);
    mButtons = new RadioGroup(context);
    mButtons.setId(QuestionWidget.newUniqueId());
    mReadOnly = prompt.isReadOnly() || readOnlyOverride;
    mAnswers = new HashMap<String, String>();

    String currentAnswer = prompt.getAnswerText();

    // the format of the query should be something like this:
    // query="instance('cities')/root/item[state=/data/state and county=/data/county]"

    // "query" is what we're using to notify that this is an
    // itemset widget.
    String nodesetStr = prompt.getQuestion().getAdditionalAttribute(null, "query");

    // parse out the list name, between the ''
    String list_name = nodesetStr.substring(nodesetStr.indexOf("'") + 1,
        nodesetStr.lastIndexOf("'"));

    // isolate the string between between the [ ] characters
    String queryString = nodesetStr.substring(nodesetStr.indexOf("[") + 1,
        nodesetStr.lastIndexOf("]"));

    StringBuilder selection = new StringBuilder();
    // add the list name as the first argument, which will always be there
    selection.append("list_name=?");

    // check to see if there are any arguments
    if (queryString.indexOf("=") != -1) {
      selection.append(" and ");
    }

    // can't just split on 'and' or 'or' because they have different
    // behavior, so loop through and break them off until we don't have any
    // more
    // must include the spaces in indexOf so we don't match words like
    // "land"
    int andIndex = -1;
    int orIndex = -1;
    ArrayList<String> arguments = new ArrayList<String>();
    while ((andIndex = queryString.indexOf(" and ")) != -1
        || (orIndex = queryString.indexOf(" or ")) != -1) {
      if (andIndex != -1) {
        String subString = queryString.substring(0, andIndex);
        String pair[] = subString.split("=");
        if (pair.length == 2) {
          selection.append(pair[0].trim() + "=? and ");
          arguments.add(pair[1].trim());
        } else {
          // parse error
        }
        // move string forward to after " and "
        queryString = queryString.substring(andIndex + 5, queryString.length());
        andIndex = -1;
      } else if (orIndex != -1) {
        String subString = queryString.substring(0, orIndex);
        String pair[] = subString.split("=");
        if (pair.length == 2) {
          selection.append(pair[0].trim() + "=? or ");
          arguments.add(pair[1].trim());
        } else {
          // parse error
        }

        // move string forward to after " or "
        queryString = queryString.substring(orIndex + 4, queryString.length());
        orIndex = -1;
      }
    }

    // parse the last segment (or only segment if there are no 'and' or 'or'
    // clauses
    String pair[] = queryString.split("=");
    if (pair.length == 2) {
      selection.append(pair[0].trim() + "=?");
      arguments.add(pair[1].trim());
    }
    if (pair.length == 1) {
      // this is probably okay, because then you just list all items in
      // the list
    } else {
      // parse error
    }

    // +1 is for the list_name
    String[] selectionArgs = new String[arguments.size() + 1];

    boolean nullArgs = false; // can't have any null arguments
    selectionArgs[0] = list_name; // first argument is always listname

    // loop through the arguments, evaluate any expressions
    // and build the query string for the DB
    for (int i = 0; i < arguments.size(); i++) {
      XPathExpression xpr = null;
      try {
        xpr = XPathParseTool.parseXPath(arguments.get(i));
      } catch (XPathSyntaxException e) {
        e.printStackTrace();
        TextView error = new TextView(context);
        error.setText("XPathParser Exception:  \"" + arguments.get(i) + "\"");
        addView(error);
        break;
      }

      if (xpr != null) {
        FormDef form = Collect.getInstance().getFormController().getFormDef();
        TreeElement mTreeElement = form.getMainInstance().resolveReference(prompt.getIndex().getReference());
        EvaluationContext ec = new EvaluationContext(form.getEvaluationContext(),
            mTreeElement.getRef());
        Object value = xpr.eval(form.getMainInstance(), ec);

        if (value == null) {
          nullArgs = true;
        } else {
          if (value instanceof XPathNodeset) {
            XPathNodeset xpn = (XPathNodeset) value;
            value = xpn.getValAt(0);
          }

          selectionArgs[i + 1] = value.toString();
        }
      }
    }

    File itemsetFile = new File(Collect.getInstance().getFormController().getMediaFolder().getAbsolutePath() +
        "/itemsets.csv");
    if (nullArgs) {
      // we can't try to query with null values else it blows up
      // so just leave the screen blank
      // TODO: put an error?
    } else if (itemsetFile.exists()) {
      ItemsetDbAdapter ida = new ItemsetDbAdapter();
      ida.open();

      // name of the itemset table for this form
      String pathHash = ItemsetDbAdapter.getMd5FromString(itemsetFile.getAbsolutePath());
      try {
        Cursor c = ida.query(pathHash, selection.toString(), selectionArgs);
        if (c != null) {
          c.move(-1);
          while (c.moveToNext()) {
            String label = "";
            String val = "";
            // try to get the value associated with the label:lang
            // string if that doen't exist, then just use label
            String lang = "";
            if (Collect.getInstance().getFormController().getLanguages() != null
                && Collect.getInstance().getFormController().getLanguages().length > 0) {
              lang = Collect.getInstance().getFormController().getLanguage();
            }

            // apparently you only need the double quotes in the
            // column name when creating the column with a :
            // included
            String labelLang = "label" + "::" + lang;
            int langCol = c.getColumnIndex(labelLang);
            if (langCol == -1) {
              label = c.getString(c.getColumnIndex("label"));
            } else {
              label = c.getString(c.getColumnIndex(labelLang));
            }

            // the actual value is stored in name
            val = c.getString(c.getColumnIndex("name"));
            mAnswers.put(label, val);

            RadioButton rb = new RadioButton(context);
            rb.setOnCheckedChangeListener(this);
            rb.setText(label);
            rb.setTextSize(mAnswerFontSize);
            mButtons.addView(rb);
            // have to add it to the radiogroup before checking it,
            // else it lets two buttons be checked...
            if (currentAnswer != null
                && val.compareTo(currentAnswer) == 0) {
              rb.setChecked(true);
            }
          }
          c.close();
        }
      } finally {
        ida.close();
      }

      addView(mButtons);
    } else {
      TextView error = new TextView(context);
      error.setText(getContext().getString(R.string.file_missing, itemsetFile.getAbsolutePath()));
      addView(error);
    }

  }

  @Override
  public void clearAnswer() {
    mButtons.clearCheck();
    mAnswer = null;
  }

  @Override
  public IAnswerData getAnswer() {
    if (mAnswer == null) {
      return null;
    } else {
      return new StringData(mAnswer);
    }
  }

  @Override
  public void setFocus(Context context) {
    // Hide the soft keyboard if it's showing.
    InputMethodManager inputManager = (InputMethodManager) context
        .getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.hideSoftInputFromWindow(this.getWindowToken(), 0);
  }

  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (event.isAltPressed() == true) {
      return false;
    }
    return super.onKeyDown(keyCode, event);
  }

  @Override
  public void setOnLongClickListener(OnLongClickListener l) {
    mButtons.setOnLongClickListener(l);
    for (int i = 0; i < mButtons.getChildCount(); i++) {
      mButtons.getChildAt(i).setOnLongClickListener(l);
    }
  }

  @Override
  public void cancelLongPress() {
    super.cancelLongPress();
    mButtons.cancelLongPress();
    for (int i = 0; i < mButtons.getChildCount(); i++) {
      mButtons.getChildAt(i).cancelLongPress();
    }
  }

  @Override
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if (isChecked) {
      mAnswer = mAnswers.get((String) buttonView.getText());
    }
  }

}
