/*
 * Copyright 2008 ZXing authors
 *
 * 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 com.google.zxing.client.rim;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.client.j2me.LCDUIImageLuminanceSource;
import com.google.zxing.client.rim.persistence.AppSettings;
import com.google.zxing.client.rim.persistence.history.DecodeHistory;
import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;
import com.google.zxing.client.rim.util.Log;
import com.google.zxing.client.rim.util.ReasonableTimer;
import com.google.zxing.client.rim.util.URLDecoder;
import net.rim.blackberry.api.browser.Browser;
import net.rim.blackberry.api.browser.BrowserSession;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.DialogFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.lcdui.Image;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;

/**
 * The main appication menu screen.
 *
 * This code was contributed by LifeMarks.
 *
 * @author Matt York (matt@lifemarks.mobi)
 */
final class ZXingLMMainScreen extends MainScreen {

  private final ZXingUiApplication app;
  private final QRCapturedJournalListener imageListener;
  private PopupScreen popup;
  private final Reader reader;
  private final Hashtable readerHints;

  ZXingLMMainScreen() {
    super(DEFAULT_MENU | DEFAULT_CLOSE);
    setTitle(new LabelField("ZXing", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));
    setChangeListener(null);

    Manager vfm = new VerticalFieldManager(USE_ALL_WIDTH);
    FieldChangeListener buttonListener = new ButtonListener();

    //0
    Field snapButton = new ButtonField("Snap", FIELD_HCENTER | ButtonField.CONSUME_CLICK | USE_ALL_WIDTH);
    snapButton.setChangeListener(buttonListener);
    vfm.add(snapButton);

    //1
    Field historyButton = new ButtonField("History", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
    historyButton.setChangeListener(buttonListener);
    vfm.add(historyButton);

    //2
    Field settingsButton = new ButtonField("Settings", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
    settingsButton.setChangeListener(buttonListener);
    vfm.add(settingsButton);

    //3
    Field aboutButton = new ButtonField("About", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
    aboutButton.setChangeListener(buttonListener);
    vfm.add(aboutButton);

    //4
    Field helpButton = new ButtonField("Help", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
    helpButton.setChangeListener(buttonListener);
    vfm.add(helpButton);

    vfm.setChangeListener(null);
    add(vfm);


    app = (ZXingUiApplication) UiApplication.getUiApplication();
    imageListener = new QRCapturedJournalListener(this);

    reader = new MultiFormatReader();
    readerHints = new Hashtable(1);
    readerHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
  }


  /**
   * Handles the newly created file. If the file is a jpg image, from the camera, the images is assumed to be
   * a qrcode and decoding is attempted.
   */
  void imageSaved(String imagePath) {
    Log.info("Image saved: " + imagePath);
    app.removeFileSystemJournalListener(imageListener);
    if (imagePath.endsWith(".jpg") && imagePath.indexOf("IMG") >= 0) // a blackberry camera image file
    {
      Log.info("imageSaved - Got file: " + imagePath);
      Camera.getInstance().exit();
      Log.info("camera exit finished");
      app.requestForeground();

      DialogFieldManager manager = new DialogFieldManager();
      popup = new PopupScreen(manager);
      manager.addCustomField(new LabelField("Decoding image..."));

      app.pushScreen(popup); // original
      Log.info("started progress screen.");

      Runnable fct = new FileConnectionThread(imagePath);
      Log.info("Starting file connection thread.");
      app.invokeLater(fct);
      Log.info("Finished file connection thread.");
    } else {
      Log.error("Failed to locate camera image.");
    }
  }

  /**
   * Closes the application and persists all required data.
   */
  public void close() {
    app.removeFileSystemJournalListener(imageListener);
    DecodeHistory.getInstance().persist();
    super.close();
  }

  /**
   * This method is overriden to remove the 'save changes' dialog when exiting.
   */
  public boolean onSavePrompt() {
    setDirty(false);
    return true;
  }

  /**
   * Listens for selected buttons and starts the required screen.
   */
  private final class ButtonListener implements FieldChangeListener {
    public void fieldChanged(Field field, int context) {
      Log.debug("*** fieldChanged: " + field.getIndex());
      switch (field.getIndex()) {
        case 0: // snap
          try {
            app.addFileSystemJournalListener(imageListener);
            Camera.getInstance().invoke(); // start camera
            return;
          }
          catch (Exception e) {
            Log.error("!!! Problem invoking camera.!!!: " + e);
          }
          break;
        case 1: // history
          app.pushScreen(new HistoryScreen());
          break;
        case 2: // settings
          app.pushScreen(new SettingsScreen());
          break;
        case 3: //about
          app.pushScreen(new AboutScreen());
          break;
        case 4: //help
          app.pushScreen(new HelpScreen());
          break;
      }
    }

  }

  /**
   * Thread that decodes the newly created image. If the image is successfully decoded and the data is a URL,
   * the browser is invoked and pointed to the given URL.
   */
  private final class FileConnectionThread implements Runnable {

    private final String imagePath;

    private FileConnectionThread(String imagePath) {
      this.imagePath = imagePath;
    }

    public void run() {
      FileConnection file = null;
      InputStream is = null;
      Image capturedImage = null;
      try {
        file = (FileConnection) Connector.open("file://" + imagePath, Connector.READ_WRITE);
        is = file.openInputStream();
        capturedImage = Image.createImage(is);
      } catch (IOException e) {
        Log.error("Problem creating image: " + e);
        removeProgressBar();
        invalidate();
        showMessage("An error occured processing the image.");
        return;
      } finally {
        try {
          if (is != null) {
            is.close();
          }
          if (file != null && file.exists()) {
            if (file.isOpen()) {
              file.delete();              
              file.close();
            }
            Log.info("Deleted image file.");
          }
        } catch (IOException ioe) {
          Log.error("Error while closing file: " + ioe);
        }
      }

      if (capturedImage != null) {
        Log.info("Got image...");
        LuminanceSource source = new LCDUIImageLuminanceSource(capturedImage);
        BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
        Result result;
        ReasonableTimer decodingTimer = null;
        try {
          decodingTimer = new ReasonableTimer();
          Log.info("Attempting to decode image...");
          result = reader.decode(bitmap, readerHints);
          decodingTimer.finished();
        } catch (ReaderException e) {
          Log.error("Could not decode image: " + e);
          decodingTimer.finished();
          removeProgressBar();
          invalidate();
          boolean showResolutionMsg =
                  !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue();
          if (showResolutionMsg) {
            showMessage("A QR Code was not found in the image. " +
                        "We detected that the decoding process took quite a while. " +
                        "It will be much faster if you decrease your camera's resolution (640x480).");
          } else {
            showMessage("A QR Code was not found in the image.");
          }
          return;
        }
        if (result != null) {
          String resultText = result.getText();
          Log.info("result: " + resultText);
          if (isURI(resultText)) {
            resultText = URLDecoder.decode(resultText);
            removeProgressBar();
            invalidate();
            if (!decodingTimer.wasResonableTime() &&
                !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue()) {
              showMessage("We detected that the decoding process took quite a while. " +
                          "It will be much faster if you decrease your camera's resolution (640x480).");
            }
            DecodeHistory.getInstance().addHistoryItem(new DecodeHistoryItem(resultText));
            invokeBrowser(resultText);
            return;
          }
        } else {
          removeProgressBar();
          invalidate();
          showMessage("A QR Code was not found in the image.");
          return;
        }

      }

      removeProgressBar();
      invalidate();
    }

    /**
     * Quick check to see if the result of decoding the qr code was a valid uri.
     */
    private boolean isURI(String uri) {
      return uri.startsWith("http://");
    }

    /**
     * Invokes the web browser and browses to the given uri.
     */
    private void invokeBrowser(String uri) {
      BrowserSession browserSession = Browser.getDefaultSession();
      browserSession.displayPage(uri);
    }

    /**
     * Syncronized version of removing progress dialog.
     * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
     */
    private void removeProgressBar() {
      synchronized (app.getAppEventLock()) {
        if (popup != null) {
          app.popScreen(popup);
        }
      }
    }

    /**
     * Syncronized version of showing a message dialog.
     * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
     */
    private void showMessage(String message) {
      synchronized (app.getAppEventLock()) {
        Dialog.alert(message);
      }
    }
  }

}
