/*  
 * Copyright (c) 2016  LibreTasks - https://github.com/biotinker/LibreTasks  
 *  
 *  This file is free software: you may copy, redistribute and/or modify it  
 *  under the terms of the GNU General Public License as published by the  
 *  Free Software Foundation, either version 3 of the License, or (at your  
 *  option) any later version.  
 *  
 *  This file 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.  
 *  
 *  You should have received a copy of the GNU General Public License  
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.  
 *  
 * This file incorporates work covered by the following copyright and  
 * permission notice:  
 /*******************************************************************************
 * Copyright 2009, 2010 Omnidroid - http://code.google.com/p/omnidroid
 *
 * 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 libretasks.app.model;

import static libretasks.app.model.CursorHelper.*;

import java.util.ArrayList;
import java.util.HashMap;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import libretasks.app.R;
import libretasks.app.controller.Action;
import libretasks.app.controller.actions.CallPhoneAction;
import libretasks.app.controller.actions.PowerOffAction;
import libretasks.app.controller.actions.SendGmailAction;
import libretasks.app.controller.actions.SendSmsAction;
import libretasks.app.controller.actions.SetPhoneLoudAction;
import libretasks.app.controller.actions.SetPhoneSilentAction;
import libretasks.app.controller.actions.SetPhoneVibrateAction;
import libretasks.app.controller.actions.SetScreenBrightnessAction;
import libretasks.app.controller.actions.ShowAlertAction;
import libretasks.app.controller.actions.ShowNotificationAction;
import libretasks.app.controller.actions.ShowWebsiteAction;
import libretasks.app.controller.actions.TurnOffWifiAction;
import libretasks.app.controller.actions.TurnOnWifiAction;
import libretasks.app.controller.actions.UpdateTwitterStatusAction;
import libretasks.app.controller.util.ExceptionMessageMap;
import libretasks.app.controller.util.Logger;
import libretasks.app.controller.util.OmnidroidException;
import libretasks.app.model.db.DbHelper;
import libretasks.app.model.db.FailedActionParameterDbAdapter;
import libretasks.app.model.db.FailedActionsDbAdapter;
import libretasks.app.model.db.RegisteredActionDbAdapter;
import libretasks.app.model.db.RegisteredAppDbAdapter;
import libretasks.app.model.db.RuleActionDbAdapter;
import libretasks.app.model.db.RuleDbAdapter;
import libretasks.app.view.simple.UtilUI;

/**
 * This class serves as a database access layer for Failed Actions framework.
 * 
 */
public class FailedActionsDbHelper {
  private static final String TAG = FailedActionsDbHelper.class.getSimpleName();

  private DbHelper dbHelper;
  private SQLiteDatabase database;
  private FailedActionsDbAdapter failedActionsDbAdapter;
  private FailedActionParameterDbAdapter failedActionParameterDbAdapter;
  private RegisteredActionDbAdapter registeredActionDbAdapter;
  private RegisteredAppDbAdapter registeredAppDbAdapter;
  private RuleDbAdapter ruleDbAdapter;
  private RuleActionDbAdapter ruleActionDbAdapter;
  
  private Context context;
  
  // Action info constants
  private final int KEY_APP_NAME = 0;
  private final int KEY_ACTION_NAME = 1;

  public FailedActionsDbHelper(Context context) {
    this.context = context;
    dbHelper = new DbHelper(context);
    database = dbHelper.getWritableDatabase();
    failedActionsDbAdapter = new FailedActionsDbAdapter(database);
    failedActionParameterDbAdapter = new FailedActionParameterDbAdapter(database);
    registeredActionDbAdapter = new RegisteredActionDbAdapter(database);
    registeredAppDbAdapter = new RegisteredAppDbAdapter(database);
    ruleDbAdapter = new RuleDbAdapter(database);
    ruleActionDbAdapter = new RuleActionDbAdapter(database);
  }

  /**
   * Close this database helper object. Attempting to use this object after this call will cause an
   * {@link IllegalStateException} being raised.
   */
  public void close() {
    Log.i(TAG, "closing database.");
    database.close();
    dbHelper.close();
  }

  /**
   * This method gives an ArrayList of actions to be executed for a given rule. Populates the action
   * parameter fields, which may require retrieving them from the event
   * 
   * @param failureType
   *          type of failure, use ResulProcessor RESULT_FAIULRE_types;
   * @return ArrayList of queued actions, matching failureType 
   * 
   * @throws IllegalStateException
   *           when this object is already closed
   */
  public ArrayList<Action> getActions(int failureType) {
    if (!database.isOpen()) {
      throw new IllegalStateException(TAG + " is already closed.");
    }

    ArrayList<Action> actions = new ArrayList<Action>();
    
    Action action;
    String[] actionInfo;
    HashMap<String, String> actionParams;
    for (Long failedActionId : getFailedActionIds(failureType)) {
      actionInfo = getRegisteredActionInfo(failedActionId);
      if (actionInfo == null) {
        throw new IllegalArgumentException(
            "Cannot find ActionId, ApplicationName or ActionName for: " + failedActionId);
      }
      
      actionParams = getParameters(failedActionId);
      
      Cursor cursor = failedActionsDbAdapter.fetch(failedActionId);
      cursor = ruleDbAdapter.fetch(getLongFromCursor(cursor, FailedActionsDbAdapter.KEY_RULEID));
      try {
        action = getAction(actionInfo[KEY_APP_NAME], actionInfo[KEY_ACTION_NAME], actionParams);
        action.setRuleName(getStringFromCursor(cursor, RuleDbAdapter.KEY_RULENAME));
        action.setNotification(getBooleanFromCursor(cursor, RuleDbAdapter.KEY_NOTIFICATION));
        action.setDatabaseId(failedActionId);
        action.setActionType(Action.FAILED_ACTION);
        actions.add(action); 
      } catch (OmnidroidException e) {
        Logger.w(TAG, e.toString(), e);
        Logger.w(TAG, e.getLocalizedMessage());
      }
      cursor.close();
    }
    
    return actions;
  }
  
  private HashMap<String, String> getParameters (Long failedActionId) {
    
    HashMap<String, String> parameters = new HashMap<String, String>();
    Cursor cursor = failedActionParameterDbAdapter.fetchAll(failedActionId, null, null); 
    while (cursor.moveToNext()) {
      parameters.put(getStringFromCursor(cursor, FailedActionParameterDbAdapter.KEY_ACTIONPARAMETERNAME),
          getStringFromCursor(cursor, FailedActionParameterDbAdapter.KEY_FAILEDACTIONPARAMETERDATA));
    }
    cursor.close();
    return parameters;
  } 
  
  /**
   * This method initializes an Action object from a given action name and parameters
   * 
   * @param appName
   *          Name of the application
   * @param actionName
   *          Name of the action
   * @param actionParams
   *          Parameters required to initialize the action
   * @return An action object
   * @throws OmnidroidException
   *           if the given action cannot be initialized by this method
   */
  private Action getAction(String appName, String actionName, HashMap<String, String> actionParams)
      throws OmnidroidException {

    if (appName.equals(SendSmsAction.APP_NAME) 
        && actionName.equals(SendSmsAction.ACTION_NAME)) {
      return new SendSmsAction(actionParams);
    } else if (appName.equals(CallPhoneAction.APP_NAME)
        && actionName.equals(CallPhoneAction.ACTION_NAME)) {
      return new CallPhoneAction(actionParams);
    } else if (appName.equals(SendGmailAction.APP_NAME)
        && actionName.equals(SendGmailAction.ACTION_NAME)) {
      return new SendGmailAction(actionParams);
    } else if (appName.equals(ShowAlertAction.APP_NAME)
        && actionName.equals(ShowAlertAction.ACTION_NAME)) {
      return new ShowAlertAction(actionParams);
    } else if (appName.equals(ShowNotificationAction.APP_NAME)
        && actionName.equals(ShowNotificationAction.ACTION_NAME)) {
      return new ShowNotificationAction(actionParams);
    } else if (appName.equals(ShowWebsiteAction.APP_NAME)
        && actionName.equals(ShowWebsiteAction.ACTION_NAME)) {
      return new ShowWebsiteAction(actionParams);
    } else if (appName.equals(SetScreenBrightnessAction.APP_NAME)
        && actionName.equals(SetScreenBrightnessAction.ACTION_NAME)) {
      return new SetScreenBrightnessAction(actionParams);
    } else if (appName.equals(SetPhoneLoudAction.APP_NAME)
        && actionName.equals(SetPhoneLoudAction.ACTION_NAME)) {
      return new SetPhoneLoudAction(actionParams);
    } else if (appName.equals(SetPhoneSilentAction.APP_NAME)
        && actionName.equals(SetPhoneSilentAction.ACTION_NAME)) {
      return new SetPhoneSilentAction(actionParams);
    } else if (appName.equals(SetPhoneVibrateAction.APP_NAME)
        && actionName.equals(SetPhoneVibrateAction.ACTION_NAME)) {
      return new SetPhoneVibrateAction(actionParams);
    } else if (appName.equals(TurnOffWifiAction.APP_NAME)
        && actionName.equals(TurnOffWifiAction.ACTION_NAME)) {
      return new TurnOffWifiAction(actionParams);
    } else if (appName.equals(TurnOnWifiAction.APP_NAME)
        && actionName.equals(TurnOnWifiAction.ACTION_NAME)) {
      return new TurnOnWifiAction(actionParams);
    } else if (appName.equals(UpdateTwitterStatusAction.APP_NAME)
        && actionName.equals(UpdateTwitterStatusAction.ACTION_NAME)) {
      return new UpdateTwitterStatusAction(actionParams);
    } else if (appName.equals(PowerOffAction.APP_NAME)
            && actionName.equals(PowerOffAction.ACTION_NAME)) {
        return new PowerOffAction(actionParams);
    } else {
      Log.d(TAG, "doesn't catch AppName is: " + appName + " and actionName is: " + actionName);
      throw new OmnidroidException(120003, ExceptionMessageMap.getMessage(new Integer(120003)
          .toString()));
    }
  }
  
  /**
   * This method returns an ArrayList of action ids which are to be executed for a given failed
   * 
   * @param failedId
   *          The failed Id
   * @return ArrayList of Action Ids for actions to be executed
   * @throws IllegalStateException
   *           when this object is already closed
   */
  private ArrayList<Long> getFailedActionIds(int failureType) {
    if (!database.isOpen()) {
      throw new IllegalStateException(TAG + " is already closed.");
    }

    Cursor cursor = failedActionsDbAdapter.fetchAll(null, null, failureType);
    ArrayList<Long> ruleActionIds = new ArrayList<Long>();
    while (cursor.moveToNext()) {
      ruleActionIds.add(getLongFromCursor(cursor, FailedActionsDbAdapter.KEY_FAILEDACTIONID));
    }
    cursor.close();
    return ruleActionIds;
  }

  /**
   * This method gives the registered action name and its application name for a failed action
   * 
   * @param failedActionId
   *          The failed Action's Id
   * @return String array {application name, action name}. Null if cannot find action name,
   *         application name or action id
   * @throws IllegalStateException
   *           when this object is already closed
   */
  private String[] getRegisteredActionInfo(Long failedActionId) {
    if (!database.isOpen()) {
      throw new IllegalStateException(TAG + " is already closed.");
    }

    Long actionId;
    Long appId;
    String actionName;
    String appName;

    Cursor cursor = failedActionsDbAdapter.fetch(failedActionId);
    if (cursor.moveToFirst()) {
      actionId = getLongFromCursor(cursor, FailedActionsDbAdapter.KEY_ACTIONID);
      cursor.close();
    } else {
      cursor.close();
      return null;
    }

    cursor = registeredActionDbAdapter.fetch(actionId);
    if (cursor.moveToFirst()) {
      actionName = getStringFromCursor(cursor, RegisteredActionDbAdapter.KEY_ACTIONNAME);
      appId = getLongFromCursor(cursor, RegisteredActionDbAdapter.KEY_APPID);
      cursor.close();
    } else {
      cursor.close();
      return null;
    }

    cursor = registeredAppDbAdapter.fetch(appId);
    if (cursor.moveToFirst()) {
      appName = getStringFromCursor(cursor, RegisteredAppDbAdapter.KEY_APPNAME);
      cursor.close();
    } else {
      cursor.close();
      return null;
    }

    return new String[] { appName, actionName };
  }
  public boolean delete(long failedActionId) {
    return failedActionParameterDbAdapter.delete(failedActionId) && 
        failedActionsDbAdapter.delete(failedActionId);
  }
  
  public long insert (Intent intent, int failureType, String message ) {
 
    long ruleActionId = intent.getLongExtra(Action.DATABASE_ID, -1);
    Logger.w(TAG, "ruleActionId aris" + ruleActionId);
    Cursor cursor = ruleActionDbAdapter.fetch(ruleActionId);
    long ruleId = getLongFromCursor(cursor, RuleActionDbAdapter.KEY_RULEID);
    long actionId = getLongFromCursor(cursor, RuleActionDbAdapter.KEY_ACTIONID);
    long failedActionId = failedActionsDbAdapter.insert(ruleId, actionId, failureType, message);
    
    Logger.w(TAG, "inserting action into database, failure type "+ failureType);
    
    Bundle params = intent.getExtras();
    
    for (String paramName : params.keySet()) {
      if (!paramName.equals(Action.DATABASE_ID) && !paramName.equals(Action.ACTION_TYPE) &&
          !paramName.equals(Action.NOTIFICATION)) {
        failedActionParameterDbAdapter.insert(failedActionId, paramName, 
            params.getString(paramName));
      }
    }
    return failedActionId;
  }

  public boolean deleteAll () {
    return failedActionParameterDbAdapter.deleteAll() &&
        failedActionsDbAdapter.deleteAll();
  }
  /**
   * updates failureType with new result;
   * 
   * @param intent
   *           intent of the action to be updated
   * @param result
   *           new cause of failure
   * @param result 
   * @param message 
   */
  public void update(Intent intent, int result, String message)  {
      failedActionsDbAdapter.update(intent.getLongExtra(Action.DATABASE_ID, -1), 
          null, null, result, message);
  }
  /**
   * 
   */
  public void deleteOldActions() {
    if (!database.isOpen()) {
      throw new IllegalStateException(TAG + " is already closed.");
    }

    Cursor cursor = failedActionsDbAdapter.fetchOldActions();
    while (cursor.moveToNext()) {
      UtilUI.showNotification(context, UtilUI.NOTIFICATION_RULE, context.getString(R.string.libretasks), getStringFromCursor(cursor, FailedActionsDbAdapter.KEY_MESSAGE));
      failedActionsDbAdapter.delete(getLongFromCursor(cursor, 
          FailedActionsDbAdapter.KEY_FAILEDACTIONID));
    }
    cursor.close();
  }
}