package com.biermacht.brews.frontend.fragments;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.biermacht.brews.R;
import com.biermacht.brews.database.DatabaseAPI;
import com.biermacht.brews.frontend.AddRecipeActivity;
import com.biermacht.brews.frontend.BrewTimerActivity;
import com.biermacht.brews.frontend.DisplayRecipeActivity;
import com.biermacht.brews.frontend.DisplayStyleActivity;
import com.biermacht.brews.frontend.EditFermentationProfileActivity;
import com.biermacht.brews.frontend.EditMashProfileActivity;
import com.biermacht.brews.frontend.EditRecipeNotesActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddFermentableActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddHopsActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddMiscActivity;
import com.biermacht.brews.frontend.IngredientActivities.AddYeastActivity;
import com.biermacht.brews.frontend.IngredientActivities.EditRecipeActivity;
import com.biermacht.brews.frontend.adapters.DisplayRecipeCollectionPagerAdapter;
import com.biermacht.brews.frontend.adapters.RecipeArrayAdapter;
import com.biermacht.brews.ingredient.Ingredient;
import com.biermacht.brews.recipe.MashStep;
import com.biermacht.brews.recipe.Recipe;
import com.biermacht.brews.utils.Constants;
import com.biermacht.brews.utils.DriveActivity;
import com.biermacht.brews.utils.Units;
import com.biermacht.brews.utils.Utils;
import com.biermacht.brews.utils.comparators.RecipeDateComparator;
import com.biermacht.brews.utils.comparators.RecipeModifiedComparator;
import com.biermacht.brews.utils.comparators.RecipeNameComparator;
import com.biermacht.brews.utils.interfaces.BiermachtFragment;
import com.biermacht.brews.xml.RecipeXmlWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;

public class RecipesFragment extends Fragment implements BiermachtFragment {

  // Layout resource
  private static int resource = R.layout.fragment_recipes;

  // The parent view group.
  ViewGroup pageView;

  // Recipe List stuff
  private RecipeArrayAdapter mAdapter;
  private AdapterView.OnItemClickListener mClickListener;
  private AdapterView.OnItemLongClickListener mLongClickListener;
  private ArrayList<Recipe> recipeList;

  // Context menu items
  private ArrayList<String> menuItems;

  // Holds the recipe for which the current context menu (long press)
  // action is being performed.
  private Recipe contextActionRecipe;

  // Context
  private Context context;
  private DatabaseAPI databaseApi;

  // Declare views here
  private ListView listView;
  private TextView noRecipesView;
  private LinearLayout detailsView;

  // Currently displayed AlertDialog
  private AlertDialog currentAlert;

  // Fields used when running on a tablet
  public boolean isTablet = false;
  private DisplayRecipeCollectionPagerAdapter cpAdapter;
  private ViewPager mViewPager;
  public int currentSelectedIndex = 0;

  // Contextual action bar (CAB) stuff
  ActionMode.Callback mActionModeCallback;
  ActionMode mActionMode;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    Log.d("RecipesFragment", "Starting onCreateView()");

    // Get views
    pageView = (RelativeLayout) inflater.inflate(resource, container, false);
    listView = (ListView) pageView.findViewById(R.id.recipe_list);
    noRecipesView = (TextView) pageView.findViewById(R.id.no_recipes_view);

    // Get Context
    context = getActivity();
    databaseApi = new DatabaseAPI(context);

    // Create recipe adapter.
    recipeList = new ArrayList<Recipe>();
    mAdapter = new RecipeArrayAdapter(context, recipeList, this);

    // Set adapter for listView
    listView.setAdapter(mAdapter);

    // Search for the details view.  If it exists, it means we're
    // running on a tablet, and we should inflate the details.
    detailsView = (LinearLayout) pageView.findViewById(R.id.details_view);
    if (detailsView != null) {
      Log.d("RecipesFragment", "Found detailsView - running on tablet");
      mViewPager = (ViewPager) detailsView.findViewById(R.id.pager);
      isTablet = true;
    }

    // Callback which handles the creation and operation of the contextual action bar when
    // a Recipe is long-pressed.
    mActionModeCallback = new ActionMode.Callback() {

      @Override
      public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Called when the action mode is created; startActionMode() was called
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.context_recipe_selected, menu);
        return true;
      }

      @Override
      public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        // Called each time the action mode is shown. Always called after onCreateActionMode, but
        // may be called multiple times if the mode is invalidated.
        return false; // Return false if nothing is done
      }

      @Override
      public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        // Declare Intent used to start new apps
        Intent i;

        // Get the recipe here, in case it has changed since the user long pressed the list item.
        contextActionRecipe = recipeList.get(currentSelectedIndex);

        // Determine which context item was selected.
        switch (item.getItemId()) {
          case R.id.menu_delete_recipe:
            deleteAlert(contextActionRecipe).show();
            return true;

          case R.id.menu_scale_recipe:
            scaleAlert(contextActionRecipe).show();
            return true;

          case R.id.menu_copy_recipe:
            // TODO: Ask user for new name, animate new recipe entering list.
            Recipe copy = databaseApi.createRecipeFromExisting(contextActionRecipe);
            copy.setRecipeName(contextActionRecipe.getRecipeName() + " - Copy");
            recipeList.add(mAdapter.getPosition(contextActionRecipe) + 1, copy);
            mAdapter.notifyDataSetChanged();
            Snackbar.make(listView, R.string.recipe_copied, Snackbar.LENGTH_LONG).show();
            copy.save(context);
            return true;

          case R.id.edit_recipe:
            i = new Intent(context, EditRecipeActivity.class);
            i.putExtra(Constants.KEY_RECIPE_ID, contextActionRecipe.getId());
            i.putExtra(Constants.KEY_RECIPE, contextActionRecipe);
            startActivity(i);
            return true;

          case R.id.export_recipe:
            exportXMLAlert(contextActionRecipe).show();
            return true;

          case R.id.export_recipe_txt:
            exportTextAlert(contextActionRecipe).show();
            return true;

          case R.id.edit_fermentation_profile:
            i = new Intent(context, EditFermentationProfileActivity.class);
            i.putExtra(Constants.KEY_RECIPE_ID, contextActionRecipe.getId());
            i.putExtra(Constants.KEY_RECIPE, contextActionRecipe);
            startActivity(i);
            return true;

          case R.id.brew_timer:
            i = new Intent(context, BrewTimerActivity.class);
            i.putExtra(Constants.KEY_RECIPE_ID, contextActionRecipe.getId());
            i.putExtra(Constants.KEY_RECIPE, contextActionRecipe);
            startActivity(i);
            return true;

          case R.id.edit_mash_profile:
            i = new Intent(context, EditMashProfileActivity.class);
            i.putExtra(Constants.KEY_RECIPE_ID, contextActionRecipe.getId());
            i.putExtra(Constants.KEY_RECIPE, contextActionRecipe);
            i.putExtra(Constants.KEY_PROFILE_ID, contextActionRecipe.getMashProfile().getId());
            i.putExtra(Constants.KEY_PROFILE, contextActionRecipe.getMashProfile());
            startActivity(i);
            return true;

          case R.id.recipe_notes:
            i = new Intent(context, EditRecipeNotesActivity.class);
            i.putExtra(Constants.KEY_RECIPE, contextActionRecipe);
            i.putExtra(Constants.KEY_RECIPE_ID, contextActionRecipe.getId());
            startActivity(i);
            return true;
        }
        return false;
      }

      @Override
      public void onDestroyActionMode(ActionMode mode) {
        // Called when the user exits the action mode

        // Nullify the CAB.
        mActionMode = null;

        // Clear any selections in the list.
        mAdapter.clearChecks();
        mAdapter.notifyDataSetChanged();
      }
    };

    // Set up the onClickListener for when a Recipe is selected
    // in the main recipe list.
    mClickListener = new AdapterView.OnItemClickListener() {
      public void onItemClick(AdapterView<?> parentView, View childView, int pos, long id) {

        // If the contextual action bar is shown, and we've clicked another recipe,
        // while we're in tablet mode, we need to cancel the CAB so that it isn't
        // confusing.  The other option would be to update contextActionRecipe.
        if (mActionMode != null && isTablet) {
          mActionMode.finish();
        }

        // If we're running on a tablet, update the details view.
        // Otherwise, open the DisplayRecipeActivity to display the recipe.
        if (isTablet) {
          Log.d("RecipesFragment", "Running on tablet - update details");
          currentSelectedIndex = pos;
          updateTabletDetailsView(recipeList.get(pos));
          mAdapter.notifyDataSetChanged();
        }
        else {
          Log.d("RecipesFragment", "Launching DisplayRecipeActivity");
          Intent intent = new Intent(context, DisplayRecipeActivity.class);
          intent.putExtra(Constants.KEY_RECIPE, recipeList.get(pos));
          startActivity(intent);
        }
      }
    };

    // Set up the onLongClickListener for when a Recipe is long clicked.
    mLongClickListener = new AdapterView.OnItemLongClickListener() {
      @Override
      public boolean onItemLongClick(AdapterView<?> adapterView, View view, int pos, long l) {
        // Mark this list item and only this list item as selected.
        mAdapter.clearChecks();
        mAdapter.setChecked(pos, true);
        mAdapter.notifyDataSetChanged();
        currentSelectedIndex = pos;

        // If the Contextual action bar is already being displayed, don't start a new action bar.
        if (mActionMode != null) {
          // Return true so that onItemClick is not invoked.
          return true;
        }

        // Otherwise, start the CAB.
        mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(mActionModeCallback);

        // Return true to indicate we have handled the action.  This prevents the onClickListener from
        // being called after the onItemLongClick event.
        return true;
      }
    };

    // Set up listView with title and ArrayAdapter
    updateRecipesFromDatabase();
    listView.setOnItemClickListener(mClickListener);
    listView.setOnItemLongClickListener(mLongClickListener);

    // Turn on options menu
    setHasOptionsMenu(true);

    Log.d("RecipesFragment", "Exiting onCreateView()");
    return pageView;
  }

  public void recipesExportedSnackbar() {
    Snackbar.make(listView, R.string.recipe_exported, Snackbar.LENGTH_LONG).show();
  }

  @Override
  public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.activity_main, menu);
  }

  public void setCorrectView() {
    if (recipeList.size() == 0) {
      noRecipesView.setVisibility(View.VISIBLE);
      listView.setVisibility(View.GONE);
      if (isTablet) {
        // The view pager may not always be defined.
        mViewPager.setVisibility(View.GONE);
      }
    }
    else {
      noRecipesView.setVisibility(View.GONE);
      listView.setVisibility(View.VISIBLE);
      if (isTablet) {
        mViewPager.setVisibility(View.VISIBLE);
      }
    }
  }

  public void updateTabletDetailsView(Recipe r) {
    // ViewPager and pagerAdapter for Slidy tabs!
    cpAdapter = new DisplayRecipeCollectionPagerAdapter(getChildFragmentManager(), r, context);

    // Set Adapter
    mViewPager = (ViewPager) detailsView.findViewById(R.id.pager);
    mViewPager.setAdapter(cpAdapter);

    // Set to the first page - the ingredients list.
    mViewPager.setCurrentItem(0);
  }

  /**
   * Returns a builder for an alert which prompts the user if they would like to delete the given
   * Recipe.  If the user selects yes, the Recipe will be deleted.  If the user cancels the alert,
   * the Recipe will not be deleted.
   */
  private AlertDialog.Builder deleteAlert(final Recipe r) {
    return new AlertDialog.Builder(context)
            .setTitle("Confirm Delete")
            .setMessage("Do you really want to delete '" + r.getRecipeName() + "'?")
            .setIcon(android.R.drawable.ic_delete)
            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                Log.d("RecipesFragment", "Deleting recipe: " + r);
                recipeList.remove(r);
                mAdapter.notifyDataSetChanged();
                databaseApi.deleteRecipe(r);
                if (isTablet) {
                  // If we're running on a tablet, we should set the current selected item to 0
                  // and update the details view.  Otherwise, the details view will remain stuck on the 
                  // current (now deleted) recipe.
                  currentSelectedIndex = (currentSelectedIndex > 0) ? currentSelectedIndex - 1 : 0;
                  if (recipeList.size() != 0) {
                    // Only update the details view if there is a recipe to show.
                    updateTabletDetailsView(recipeList.get(currentSelectedIndex));
                  }
                  else {
                    // Otherwise, set the correct views to show.
                    setCorrectView();
                  }
                }

                // If the last recipe was just deleted, we need to update which
                // view is being displayed.
                setCorrectView();

                // Dismiss the CAB
                mActionMode.finish();
                Log.d("RecipesFragment", "Recipe deleted");

                // Show Snackbar to indicate deletion.
                Snackbar.make(listView, R.string.recipe_deleted, Snackbar.LENGTH_LONG).show();
              }

            })

            .setNegativeButton(android.R.string.no, null);
  }

  private AlertDialog.Builder scaleAlert(final Recipe r) {
    LayoutInflater factory = LayoutInflater.from(context);
    final LinearLayout alertView = (LinearLayout) factory.inflate(R.layout.alert_view_scale, null);
    final EditText editText = (EditText) alertView.findViewById(R.id.new_volume_edit_text);

    return new AlertDialog.Builder(context)
            .setTitle("Scale Recipe")
            .setView(alertView)
            .setPositiveButton(R.string.scale, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                double newVolume = Double.parseDouble(editText.getText().toString()
                                                              .replace(",", "."));
                Utils.scaleRecipe(getActivity(), r, newVolume);
                mAdapter.notifyDataSetChanged();

                // Dismiss the CAB
                mActionMode.finish();

                // Show Snackbar to indicate completion.
                Snackbar.make(listView, R.string.recipe_scaled, Snackbar.LENGTH_LONG).show();
              }

            })

            .setNegativeButton(R.string.cancel, null);
  }

  @Override
  public void handleClick(View v) {
    // Currently, the handleClick method is only valid when running on
    // a tablet.  If we hit this method when not running on a tablet,
    // we should just return.
    if (! isTablet) {
      Log.d("RecipesFragment", "Not on tablet, do nothing");
      return;
    }

    // Get the current selected recipe.
    Recipe r = recipeList.get(currentSelectedIndex);

    if (v.getId() == R.id.add_ingredient_button) {
      // The user has pressed the add-ingredient button.  Display
      // options for which type of ingredient to add.  This button should 
      // only be present on tablets.
      Log.d("RecipesFragment", "User pressed add-ingredient button");
      currentAlert = ingredientSelectAlert().show();
    }
    else if (v.getId() == R.id.add_fermentable_button) {
      // User has chosen to add a fermentable - start the 
      // add fermentable activity.
      Intent i = new Intent(getActivity(), AddFermentableActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_hops_button) {
      // User has chosen to add a hop - start the add hop activity.
      Intent i = new Intent(getActivity(), AddHopsActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_yeast_button) {
      // User has chosen to add a yeast - start the add yeast activity.
      Intent i = new Intent(getActivity(), AddYeastActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.add_misc_button) {
      // User has chosen to add a misc - start the add misc activity.
      Intent i = new Intent(getActivity(), AddMiscActivity.class);
      i.putExtra(Constants.KEY_RECIPE, r);
      startActivity(i);

      // Dismiss the currentAlert, which should be the ingredientSelectorAlert()
      currentAlert.dismiss();
    }
    else if (v.getId() == R.id.display_style_button) {
      // Called when the display style button is pressed on the profile fragment.
      // Launches the DisplayStyleActivity.
      Intent i = new Intent(getActivity(), DisplayStyleActivity.class);
      i.putExtra(Constants.KEY_STYLE, r.getStyle());
      startActivity(i);
    }
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    // Check if we can handle this options item.  The recipes fragment
    // can handle the following events: New Recipe
    switch (item.getItemId()) {
      case R.id.menu_new_recipe:
        // The add recipe button was pressed - start the intent, indicating
        // that the display recipe activity should be started if we're 
        // not running on a tablet.
        Intent i = new Intent(getActivity(), AddRecipeActivity.class);
        i.putExtra(Constants.DISPLAY_ON_CREATE, ! isTablet);
        startActivity(i);
        return true;
    }

    // Return false if the item was unhandled.
    return false;
  }

  @Override
  public String name() {
    return "Recipes";
  }

  @Override
  public void update() {
    Log.d("RecipesFragment", "Updating RecipesFragment UI");
    if (recipeList != null && context != null) {
      updateRecipesFromDatabase();
    }
  }

  public void updateRecipesFromDatabase() {
    Log.d("RecipesFragment", "updateRecipesFromDatabase()");

    // Load all recipes from database.
    ArrayList<Recipe> loadedRecipes = databaseApi.getRecipeList();

    // Update the recipe list with the loaded recipes.
    recipeList.removeAll(recipeList);
    recipeList.addAll(loadedRecipes);

    // Sort the recipe list according to the configured strategy.
    sortRecipes();

    // Update the adapter and UI.
    mAdapter.notifyDataSetChanged();
    setCorrectView();

    // If we're running in tablet mode, try to set the details view to
    // display the most recently selected receipe.
    if (isTablet) {
      if (currentSelectedIndex < recipeList.size()) {
        Log.d("RecipesFragment", "Found recipes, set pageView");
        updateTabletDetailsView(recipeList.get(currentSelectedIndex));
      }
    }
  }

  private void sortRecipes() {
    String sortStrategy = this.getActivity().
            getSharedPreferences(Constants.PREFERENCES, Context.MODE_PRIVATE).
            getString(Constants.PREF_SORT_STRATEGY, Constants.SORT_STRATEGY_ALPHABETICAL);
    Log.d("RecipesFragment", "Sorting recipes using strategy: " + sortStrategy);

    if (sortStrategy.equals(Constants.SORT_STRATEGY_ALPHABETICAL)) {
      Collections.sort(recipeList, new RecipeNameComparator<Recipe>());
    }
    else if (sortStrategy.equals(Constants.SORT_STRATEGY_REV_ALPHABETICAL)) {
      Collections.sort(recipeList, Collections.<Recipe>reverseOrder(new RecipeNameComparator<Recipe>()));
    }
    else if (sortStrategy.equals(Constants.SORT_STRATEGY_BREW_DATE)) {
      Collections.sort(recipeList, new RecipeDateComparator<Recipe>());
    }
    else if (sortStrategy.equals(Constants.SORT_STRATEGY_REV_BREW_DATE)) {
      Collections.sort(recipeList, Collections.reverseOrder(new RecipeDateComparator<Recipe>()));
    }
    else if (sortStrategy.equals(Constants.SORT_STRATEGY_MODIFIED)) {
      Collections.sort(recipeList, new RecipeModifiedComparator<Recipe>());
    }
    else {
      Log.w("RecipesList", "Unrecognized sort strategy: " + sortStrategy);
    }
  }

  private AlertDialog.Builder exportXMLAlert(final Recipe r) {
    return new AlertDialog.Builder(getActivity())
            .setTitle("Export recipe")
            .setMessage("Export '" + r.getRecipeName() + "' to BeerXML file?")
            .setPositiveButton(R.string.local_storage, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                new ExportRecipeXML(r).execute("");
                mActionMode.finish();
                Snackbar.make(listView, R.string.recipe_exported, Snackbar.LENGTH_LONG).show();
              }

            })
            .setNeutralButton(R.string.cancel, null)
            .setNegativeButton(R.string.drive_button, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                ArrayList<Recipe> l = new ArrayList<Recipe>();
                l.add(r);
                ((DriveActivity) getActivity()).writeFile(l);
                mActionMode.finish();
              }

            });
  }

  private AlertDialog.Builder exportTextAlert(final Recipe r) {
    return new AlertDialog.Builder(getActivity())
            .setTitle("Export recipe")
            .setMessage("Export '" + r.getRecipeName() + "' to text file?")
            .setPositiveButton(R.string.local_storage, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                new ExportRecipeText(r).execute("");
                mActionMode.finish();
                Snackbar.make(listView, R.string.recipe_exported, Snackbar.LENGTH_LONG).show();
              }

            })
            .setNeutralButton(R.string.cancel, null);
            // TODO: Support exporting text to Google drive.
            /*.setNegativeButton(R.string.drive_button, new DialogInterface.OnClickListener() {

              public void onClick(DialogInterface dialog, int which) {
                ArrayList<Recipe> l = new ArrayList<Recipe>();
                l.add(r);
                ((DriveActivity) getActivity()).writeFile(l);
                mActionMode.finish();
              }

            });
            */
  }

  private AlertDialog.Builder finishedExporting(String pathToFile) {
    return new AlertDialog.Builder(getActivity())
            .setTitle("Complete")
            .setMessage("Finished exporting recipe to: \n" + pathToFile)
            .setPositiveButton(R.string.done, null);
  }

  /**
   * Returns a builder to display an alert to the User which asks them which type of ingredient to
   * add to the current selected recipe. Selections made in this alert should be handled by the
   * handleClick() method.
   */
  private AlertDialog.Builder ingredientSelectAlert() {
    // Inflater to inflate custom alert view.
    LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    // Inflate our custom layout
    View v = inflater.inflate(R.layout.alert_view_select_ingredient_type, null);

    return new AlertDialog.Builder(getActivity())
            .setTitle("Select Ingredient Type")
            .setView(v)
            .setNegativeButton(R.string.cancel, null);
  }

  // Async task to export a single recipe to XML.
  private class ExportRecipeXML extends AsyncTask<String, Void, String> {

    private ProgressDialog progress;
    private RecipeXmlWriter xmlWriter;
    private Context context;
    private Recipe r;
    private String filePrefix;

    private ExportRecipeXML(Recipe r) {
      this.context = getActivity();
      this.r = r;
      this.filePrefix = r.getRecipeName().replaceAll("\\s", "") + "-";
    }

    @Override
    protected String doInBackground(String... params) {
      xmlWriter = new RecipeXmlWriter(context);
      xmlWriter.writeRecipe(r, filePrefix);
      return "Executed";
    }

    @Override
    protected void onPostExecute(String result) {
      super.onPostExecute(result);
      progress.dismiss();
      finishedExporting(xmlWriter.getSavedFileLocation()).show();
      Log.d("ExportAllRecipes", "Finished exporting recipes");
    }

    @Override
    protected void onPreExecute() {
      super.onPreExecute();
      progress = new ProgressDialog(context);
      progress.setMessage("Exporting " + r.getRecipeName() + "...");
      progress.setIndeterminate(false);
      progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
      progress.setCancelable(false);
      progress.show();
    }

    @Override
    protected void onProgressUpdate(Void... values) {
    }
  }

  // Async task to export a single recipe to a text file.
  private class ExportRecipeText extends AsyncTask<String, Void, String> {

    private ProgressDialog progress;
    private Context context;
    private Recipe r;
    private String filePrefix;
    private String path;

    private ExportRecipeText(Recipe r) {
      this.context = getActivity();
      this.r = r;
      this.filePrefix = r.getRecipeName().replaceAll("\\s", "") + "-";
      this.path = "";
    }

    @Override
    protected String doInBackground(String... params) {
      this.path = this.writeFile();
      return "Executed";
    }

    @Override
    protected void onPostExecute(String result) {
      super.onPostExecute(result);
      progress.dismiss();
      finishedExporting(this.path).show();
      Log.d("ExportRecipeText", "Finished exporting recipe");
    }

    @Override
    protected void onPreExecute() {
      super.onPreExecute();
      progress = new ProgressDialog(context);
      progress.setMessage("Exporting " + r.getRecipeName() + "...");
      progress.setIndeterminate(false);
      progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
      progress.setCancelable(false);
      progress.show();
    }

    @Override
    protected void onProgressUpdate(Void... values) {
    }

    // writeFile writes a text file with the contents of the recipe,
    // and returns the path to the written file.
    private String writeFile() {
      File path = context.getExternalFilesDir(null);
      String fname = this.r.getRecipeName().toLowerCase().replace(" ", "-") + ".txt";
      File file = new File(path, fname);

      try {
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file));

        // First, write the recipe name to the file as the heading.
        outputStreamWriter.write(String.format("# %s\n\n", this.r.getRecipeName()));

        // Write some details about the recipe.
        outputStreamWriter.write(String.format("%s: %s\n", "Name", r.getRecipeName()));
        outputStreamWriter.write(String.format("%s: %s\n", "Type", r.getType()));
        outputStreamWriter.write(String.format("%s: %2.2f %s\n", "Size", r.getDisplayBatchSize(), Units.getVolumeUnits()));
        outputStreamWriter.write(String.format("%s: %s\n", "Style", r.getStyle().getName()));
        outputStreamWriter.write(String.format("%s: %2.2f %s\n", "Boil volume", r.getDisplayBoilSize(), Units.getVolumeUnits()));
        outputStreamWriter.write(String.format("%s: %s min\n", "Boil time", r.getBoilTime()));

        // Now, write ingredients.
        outputStreamWriter.write(String.format("\n## Ingredients\n\n"));
        outputStreamWriter.write("");
        for (Ingredient i : this.r.getIngredientList()) {
          outputStreamWriter.write(String.format(
                  "- %2.2f %-5s %s ",
                  i.getDisplayAmount(), i.getDisplayUnits(),
                  i.getName(),
                  i.getUse(), i.getTime()));
          if (i.getUse().equals(Ingredient.USE_BOIL)) {
            outputStreamWriter.write(String.format("(Boil %d min)", i.getTime()));
          } else {
            outputStreamWriter.write(String.format("(%s)", i.getUse()));
          }
          outputStreamWriter.write("\n");
        }

        // Now, write mash profile.
        if (r.getType().equals(Recipe.ALL_GRAIN)) {
          outputStreamWriter.write(String.format("\n## Mash Profile\n\n"));
          outputStreamWriter.write(r.getMashProfile().getName() + "\n\n");
          for (MashStep s : r.getMashProfile().getMashStepList()) {
            outputStreamWriter.write(String.format(
                    "- %s @ %2.2f%s, %2.2f %s, %s min\n",
                    s.getName(),
                    s.getDisplayStepTemp(), Units.getTemperatureUnits(),
                    s.getDisplayWaterToGrainRatio(), Units.getWaterToGrainUnits(),
                    s.getStepTime()));
          }
        }

        // Finally, write some stats.
        outputStreamWriter.write("\n## Calculated stats\n\n");
        outputStreamWriter.write(String.format("OG:  %2.3f\n", r.getOG()));
        outputStreamWriter.write(String.format("FG:  %2.3f\n", r.getFG()));
        outputStreamWriter.write(String.format("IBU: %2.1f\n", r.getBitterness()));
        outputStreamWriter.write(String.format("SRM: %2.1f\n", r.getColor()));
        outputStreamWriter.write(String.format("ABV: %2.1f", r.getABV()));

        // Close the file
        outputStreamWriter.close();
      }
      catch (IOException e) {
        Log.e("Exception", "File write failed: " + e.toString());
      }
      return file.getAbsolutePath();
    }
  }
}