content: /* This file is part of Aard Dictionary for Android <http://aarddict.org>.
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 3
   * as published by the Free Software Foundation.
   *
   * This program 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 <http://www.gnu.org/licenses/gpl-3.0.txt>
   * for more details.
   *
   * Copyright (C) 2010 Igor Tkach
   */
  
  package aarddict.android;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Timer;
  import java.util.TimerTask;
  import java.util.UUID;
  
  import aarddict.VerifyProgressListener;
  import aarddict.Volume;
  import android.app.AlertDialog;
  import android.app.ProgressDialog;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.content.DialogInterface.OnCancelListener;
  import android.content.DialogInterface.OnClickListener;
  import android.content.res.Resources;
  import android.net.Uri;
  import android.text.Html;
  import android.text.format.DateUtils;
  import android.text.method.LinkMovementMethod;
  import android.util.Log;
  import android.view.LayoutInflater;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.AdapterView;
  import android.widget.BaseAdapter;
  import android.widget.Button;
  import android.widget.ListView;
  import android.widget.TextView;
  import android.widget.Toast;
  import android.widget.TwoLineListItem;
  
  public final class DictionariesActivity extends BaseDictionaryActivity {
  
      private final static String     TAG           = DictionariesActivity.class
                                                            .getName();
  
      private ListView                listView;
      private Map<UUID, VerifyRecord> verifyData    = new HashMap<UUID, VerifyRecord>();
      private DictListAdapter         dataAdapter;
  
      private boolean                 aboutToFinish = false;
  
      @Override
      void onDictionaryServiceConnected() {
          Intent intent = getIntent();
          String action = intent.getAction();
  
          if (action != null && action.equals(Intent.ACTION_VIEW)) {
              final Uri data = intent.getData();
              Log.d(TAG, "Path: " + data.getPath());
              if (data != null && data.getPath() != null) {
                  Runnable r = new Runnable() {
                      public void run() {
                          final String path = data.getPath();
                          Log.d(TAG, "opening: " + path);
                          final Map<File, Exception> errors = dictionaryService
                                  .open(new File(path));
                          runOnUiThread(new Runnable() {
                              public void run() {
                                  if (errors.size() == 0) {
                                      Toast.makeText(
                                              getApplicationContext(),
                                              getString(
                                                      R.string.toastDictFileLoaded,
                                                      path), Toast.LENGTH_LONG)
                                              .show();
                                  } else {
                                      Toast.makeText(
                                              getApplicationContext(),
                                              getString(
                                                      R.string.toastDictFileFailed,
                                                      path), Toast.LENGTH_LONG)
                                              .show();
                                  }
                                  finish();
                              }
                          });
                      }
                  };
                  new Thread(r).start();
                  Log.d(TAG, "started: " + data.getPath());
              }
          }
  
          if (action != null && action.equals(ACTION_NO_DICTIONARIES)) {
              showNoDictionariesView();
          } else {
              super.onDictionaryServiceConnected();
          }
      }
  
      private void showNoDictionariesView() {
          TextView messageView = (TextView) findViewById(R.id.dictionariesMessageView);
          Button scanSDButton = (Button) findViewById(R.id.scanSDButton);
          messageView.setVisibility(View.VISIBLE);
          scanSDButton.setVisibility(View.VISIBLE);
          listView.setVisibility(View.GONE);
      }
  
      @Override
      void onDictionaryServiceReady() {
          if (aboutToFinish) {
              return;
          }
          Log.d(TAG, "service ready");
  
          if (dictionaryService.getDictionaries().isEmpty()) {
              showNoDictionariesView();
          } else {
              Intent intent = getIntent();
              String action = intent.getAction();
              Log.d(TAG, "Action: " + action);
              if (action != null && action.equals(ACTION_NO_DICTIONARIES)) {
                  aboutToFinish = true;
                  Intent next = new Intent();
                  next.setClass(this, LookupActivity.class);
                  Log.d(TAG, "Starting Lookup Activity");
                  startActivity(next);
                  finish();
              } else {
                  TextView messageView = (TextView) findViewById(R.id.dictionariesMessageView);
                  Button scanSDButton = (Button) findViewById(R.id.scanSDButton);
                  messageView.setVisibility(View.GONE);
                  scanSDButton.setVisibility(View.GONE);
                  listView.setVisibility(View.VISIBLE);
                  dataAdapter = new DictListAdapter(
                          dictionaryService.getVolumes());
                  listView.setAdapter(dataAdapter);
                  listView.setOnItemClickListener(dataAdapter);
                  listView.setOnItemLongClickListener(dataAdapter);
              }
          }
      }
  
      @Override
      void initUI() {
  
          setContentView(R.layout.dictionaries);
  
          listView = (ListView) findViewById(R.id.dictionariesList);
  
          Button scanSDButton = (Button) findViewById(R.id.scanSDButton);
          scanSDButton.setOnClickListener(new View.OnClickListener() {
              public void onClick(View v) {
                  scandSDCard();
              }
          });
  
          TextView messageView = (TextView) findViewById(R.id.dictionariesMessageView);
          messageView.setMovementMethod(LinkMovementMethod.getInstance());
          messageView.setText(Html.fromHtml(getString(R.string.noDictionaries)));
  
          String appName = getString(R.string.appName);
          setTitle(getString(R.string.titleDictionariesActivity, appName));
          try {
              loadVerifyData();
          } catch (Exception e) {
              Log.e(TAG, "Failed to load verify data", e);
          }
      }
  
      @Override
      protected void onDestroy() {
          super.onDestroy();
          if (dataAdapter != null) {
              dataAdapter.destroy();
          }
      }
  
      final class DictListAdapter extends BaseAdapter implements
              AdapterView.OnItemClickListener,
              AdapterView.OnItemLongClickListener
  
      {
          LayoutInflater     inflater;
          List<List<Volume>> volumes;
          Timer              timer              = new Timer();
          long               TIME_UPDATE_PERIOD = 60 * 1000;
  
          @SuppressWarnings({ "unchecked", "rawtypes" })
          public DictListAdapter(Map<UUID, List<Volume>> volumes) {
              this.volumes = new ArrayList();
              this.volumes.addAll(volumes.values());
              inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              timer.scheduleAtFixedRate(new TimerTask() {
                  public void run() {
                      updateView();
                  }
              }, TIME_UPDATE_PERIOD, TIME_UPDATE_PERIOD);
          }
  
          public int getCount() {
              return volumes.size();
          }
  
          public Object getItem(int position) {
              return position;
          }
  
          public long getItemId(int position) {
              return position;
          }
  
          public void destroy() {
              timer.cancel();
          }
  
          public void onItemClick(AdapterView<?> parent, View view, int position,
                  long id) {
              showDetail(position);
          }
  
          private void showDetail(int position) {
              Intent i = new Intent(DictionariesActivity.this,
                      DictionaryInfoActivity.class);
              i.putExtra("volumeId", volumes.get(position).get(0).getId());
              startActivity(i);
          }
  
          final class ProgressListener implements VerifyProgressListener {
  
              boolean        proceed       = true;
              ProgressDialog progressDialog;
              int            max;
              int            verifiedCount = 0;
  
              ProgressListener(ProgressDialog progressDialog, int max) {
                  this.progressDialog = progressDialog;
                  this.max = max;
              }
  
              public boolean updateProgress(final Volume d, final double progress) {
                  runOnUiThread(new Runnable() {
                      public void run() {
                          CharSequence m = getTitle(d, true);
                          progressDialog.setMessage(m);
                          progressDialog
                                  .setProgress((int) (100 * progress / max));
                      }
                  });
                  return proceed;
              }
  
              public void verified(final Volume d, final boolean ok) {
                  verifiedCount++;
                  Log.i(TAG, String.format("Verified %s: %s",
                          d.getDisplayTitle(), (ok ? "ok" : "corrupted")));
                  if (!ok) {
                      recordVerifyData(d.getDictionaryId(), ok);
                      progressDialog.dismiss();
                      CharSequence message = getString(R.string.msgDictCorruped,
                              getTitle(d, true));
                      showError(message);
                  } else {
                      runOnUiThread(new Runnable() {
                          public void run() {
                              Toast.makeText(
                                      DictionariesActivity.this,
                                      getString(R.string.msgDictOk,
                                              getTitle(d, true)),
                                      Toast.LENGTH_SHORT).show();
                          }
                      });
                      if (verifiedCount == max) {
                          recordVerifyData(d.getDictionaryId(), ok);
                          progressDialog.dismiss();
                      }
                  }
              }
          }
  
          private void recordVerifyData(UUID uuid, boolean ok) {
              VerifyRecord record = new VerifyRecord();
              record.uuid = uuid;
              record.ok = ok;
              record.date = new Date();
              verifyData.put(record.uuid, record);
              try {
                  saveVerifyData();
              } catch (Exception e) {
                  Log.e(TAG, "Failed to save verify data", e);
              }
              updateView();
          }
  
          private void updateView() {
              runOnUiThread(new Runnable() {
                  public void run() {
                      notifyDataSetChanged();
                  }
              });
          }
  
          private void showError(final CharSequence message) {
              runOnUiThread(new Runnable() {
                  public void run() {
                      AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
                              DictionariesActivity.this);
                      dialogBuilder
                              .setTitle(R.string.titleError)
                              .setMessage(message)
                              .setNeutralButton(R.string.btnDismiss,
                                      new OnClickListener() {
                                          public void onClick(
                                                  DialogInterface dialog,
                                                  int which) {
                                              dialog.dismiss();
                                          }
                                      });
                      dialogBuilder.show();
                  }
              });
          }
  
          public boolean onItemLongClick(AdapterView<?> parent, View view,
                  int position, long id) {
              verify(position);
              return true;
          }
  
          private void verify(int position) {
              final List<Volume> allDictVols = volumes.get(position);
              final ProgressDialog progressDialog = new ProgressDialog(
                      DictionariesActivity.this);
              progressDialog.setIndeterminate(false);
              progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
              progressDialog.setTitle(R.string.titleVerifying);
              progressDialog.setMessage(getTitle(allDictVols.get(0), false));
              progressDialog.setCancelable(true);
              final ProgressListener progressListener = new ProgressListener(
                      progressDialog, allDictVols.size());
  
              Runnable verify = new Runnable() {
                  public void run() {
                      for (Volume d : allDictVols) {
                          try {
                              d.verify(progressListener);
                          } catch (Exception e) {
                              Log.e(TAG, "There was an error verifying volume "
                                      + d.getId(), e);
                              progressListener.proceed = false;
                              progressDialog.dismiss();
                              showError(getString(R.string.msgErrorVerifying,
                                      d.getDisplayTitle(),
                                      e.getLocalizedMessage()));
                          }
                      }
                  }
              };
  
              progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
                      getString(R.string.btnCancel), new OnClickListener() {
                          public void onClick(DialogInterface dialog, int which) {
                              progressListener.proceed = false;
                          }
                      });
              progressDialog.setOnCancelListener(new OnCancelListener() {
                  public void onCancel(DialogInterface dialog) {
                      progressListener.proceed = false;
                  }
              });
              Thread t = new Thread(verify);
              t.setPriority(Thread.MIN_PRIORITY);
              t.start();
              progressDialog.show();
          }
  
          CharSequence getTitle(Volume d, boolean withVol) {
              StringBuilder s = new StringBuilder(d.getDisplayTitle(withVol));
              if (d.metadata.version != null) {
                  s.append(" ").append(d.metadata.version);
              }
              return s;
          }
  
          public View getView(int position, View convertView, ViewGroup parent) {
              List<Volume> allDictVols = volumes.get(position);
              int volCount = allDictVols.size();
              Volume d = allDictVols.get(0);
  
              TwoLineListItem view = (convertView != null) ? (TwoLineListItem) convertView
                      : createView(parent);
  
              view.getText1().setText(getTitle(d, false));
  
              Resources r = getResources();
              String articleStr = r.getQuantityString(R.plurals.articles,
                      d.metadata.article_count, d.metadata.article_count);
              String totalVolumesStr = r.getQuantityString(R.plurals.volumes,
                      d.header.of, d.header.of);
              String volumesStr = r.getQuantityString(R.plurals.volumes,
                      volCount, volCount);
              String shortInfo = r.getString(R.string.shortDictInfo, articleStr,
                      totalVolumesStr, volumesStr);
              if (verifyData.containsKey(d.getDictionaryId())) {
                  VerifyRecord record = verifyData.get(d.getDictionaryId());
                  CharSequence dateStr = DateUtils
                          .getRelativeTimeSpanString(record.date.getTime());
                  String resultStr = getString(record.ok ? R.string.verifyOk
                          : R.string.verifyCorrupted);
                  view.getText2().setText(
                          getString(R.string.msgDataIntegrityVerified, shortInfo,
                                  dateStr, resultStr));
              } else {
                  view.getText2().setText(
                          getString(R.string.msgDataIntegrityNotVerified,
                                  shortInfo));
              }
              return view;
          }
  
          private TwoLineListItem createView(ViewGroup parent) {
              TwoLineListItem item = (TwoLineListItem) inflater.inflate(
                      android.R.layout.simple_list_item_2, parent, false);
              return item;
          }
  
      }
  
      final static int MENU_INFO    = 1;
      final static int MENU_VERIFY  = 2;
      final static int MENU_REFRESH = 3;
  
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          menu.add(0, MENU_INFO, 0, R.string.mnDictDetails).setIcon(
                  android.R.drawable.ic_menu_info_details);
          menu.add(0, MENU_VERIFY, 1, R.string.mnDictVerify).setIcon(
                  android.R.drawable.ic_menu_manage);
          menu.add(0, MENU_REFRESH, 2, R.string.mnDictRefresh).setIcon(
                  R.drawable.ic_menu_refresh);
          return true;
      }
  
      @Override
      public boolean onPrepareOptionsMenu(Menu menu) {
          int selected = listView.getSelectedItemPosition();
          boolean validSelection = selected != ListView.INVALID_POSITION;
          menu.getItem(0).setEnabled(validSelection);
          menu.getItem(1).setEnabled(validSelection);
          menu.getItem(2).setEnabled(true);
          return true;
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          int selected = listView.getSelectedItemPosition();
          boolean validSelection = selected != ListView.INVALID_POSITION;
          switch (item.getItemId()) {
          case MENU_INFO:
              if (validSelection) {
                  dataAdapter.showDetail(selected);
              }
              break;
          case MENU_VERIFY:
              if (validSelection) {
                  dataAdapter.verify(selected);
              }
              break;
          case MENU_REFRESH:
              scandSDCard();
              break;
          }
          return true;
      }
  
      private void scandSDCard() {
          new Thread(new Runnable() {
              public void run() {
                  dictionaryService.refresh();
                  runOnUiThread(new Runnable() {
                      public void run() {
                          onDictionaryServiceReady();
                      }
                  });
              }
          }).start();
      }
  
      void saveVerifyData() throws IOException {
          File verifyDir = getDir("verify", 0);
          File verifyFile = new File(verifyDir, "verifydata");
          FileOutputStream fout = new FileOutputStream(verifyFile);
          ObjectOutputStream oout = new ObjectOutputStream(fout);
          oout.writeObject(verifyData);
      }
  
      @SuppressWarnings("unchecked")
      void loadVerifyData() throws IOException, ClassNotFoundException {
          File verifyDir = getDir("verify", 0);
          File verifyFile = new File(verifyDir, "verifydata");
          if (verifyFile.exists()) {
              FileInputStream fin = new FileInputStream(verifyFile);
              ObjectInputStream oin = new ObjectInputStream(fin);
              verifyData = (Map<UUID, VerifyRecord>) oin.readObject();
          }
      }
  }
  