content: /*******************************************************************************
   * Software Name : RCS IMS Stack
   *
   * Copyright (C) 2010-2016 Orange.
   *
   * 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.gsma.rcs.ri.messaging.adapter;
  
  import com.gsma.rcs.api.connection.utils.ExceptionUtil;
  import com.gsma.rcs.ri.R;
  import com.gsma.rcs.ri.RiApplication;
  import com.gsma.rcs.ri.utils.BitmapCache;
  import com.gsma.rcs.ri.utils.BitmapLoader;
  import com.gsma.rcs.ri.utils.BitmapLoader.BitmapCacheInfo;
  import com.gsma.rcs.ri.utils.ContactUtil;
  import com.gsma.rcs.ri.utils.FileUtils;
  import com.gsma.rcs.ri.utils.ImageBitmapLoader;
  import com.gsma.rcs.ri.utils.LogUtils;
  import com.gsma.rcs.ri.utils.RcsContactUtil;
  import com.gsma.rcs.ri.utils.SmileyParser;
  import com.gsma.rcs.ri.utils.Smileys;
  import com.gsma.rcs.ri.utils.Utils;
  import com.gsma.services.rcs.Geoloc;
  import com.gsma.services.rcs.RcsGenericException;
  import com.gsma.services.rcs.RcsPersistentStorageException;
  import com.gsma.services.rcs.RcsService;
  import com.gsma.services.rcs.RcsService.Direction;
  import com.gsma.services.rcs.RcsServiceNotAvailableException;
  import com.gsma.services.rcs.chat.ChatLog;
  import com.gsma.services.rcs.chat.ChatLog.Message.Content;
  import com.gsma.services.rcs.chat.ChatLog.Message.MimeType;
  import com.gsma.services.rcs.chat.ChatService;
  import com.gsma.services.rcs.contact.ContactId;
  import com.gsma.services.rcs.filetransfer.FileTransfer;
  import com.gsma.services.rcs.filetransfer.FileTransfer.ReasonCode;
  import com.gsma.services.rcs.filetransfer.FileTransferLog;
  import com.gsma.services.rcs.filetransfer.FileTransferService;
  import com.gsma.services.rcs.history.HistoryLog;
  
  import android.app.Activity;
  import android.content.Context;
  import android.database.Cursor;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.graphics.BitmapFactory.Options;
  import android.net.Uri;
  import android.support.v4.util.LruCache;
  import android.support.v4.widget.CursorAdapter;
  import android.text.SpannableStringBuilder;
  import android.text.TextUtils;
  import android.text.format.DateUtils;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.ImageView;
  import android.widget.LinearLayout.LayoutParams;
  import android.widget.TextView;
  
  import java.util.HashMap;
  import java.util.Map;
  
  public class TalkCursorAdapter extends CursorAdapter {
  
      private static final String LOGTAG = LogUtils.getTag(TalkCursorAdapter.class.getName());
  
      private static final int MAX_IMAGE_HEIGHT = 100;
      private static final int MAX_IMAGE_WIDTH = 100;
      private static final int VIEW_TYPE_RCS_CHAT_IN = 0;
      private static final int VIEW_TYPE_RCS_CHAT_OUT = 1;
      private static final int VIEW_TYPE_RCS_FILE_TRANSFER_IN = 2;
      private static final int VIEW_TYPE_RCS_FILE_TRANSFER_OUT = 3;
      private static final int VIEW_TYPE_RCS_GROUP_CHAT_EVENT = 4;
      private final ChatService mChatService;
      private final FileTransferService mFileTransferService;
      private BitmapCache bitmapCache;
      private final Activity mActivity;
      private Map<ContactId, String> mContactIdDisplayNameMap;
      private final LayoutParams mImageParams;
      private final LayoutParams mImageParamsDefault;
      private LayoutInflater mInflater;
      private final boolean mSingleChat;
      private Smileys mSmileyResources;
  
      /**
       * Constructor
       *
       * @param activity The activity
       * @param singleChat True if single chat
       * @param chatService the chat service
       * @param fileTransferService the file transfer service
       */
      public TalkCursorAdapter(Activity activity, boolean singleChat, ChatService chatService,
              FileTransferService fileTransferService) {
          super(activity, null, 0);
          mContactIdDisplayNameMap = new HashMap<>();
          mActivity = activity;
          mInflater = LayoutInflater.from(activity);
          int size100Dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100.0f,
                  mContext.getResources().getDisplayMetrics());
          mImageParams = new LayoutParams(size100Dp, size100Dp);
          Options opt = new Options();
          opt.inJustDecodeBounds = true;
          BitmapFactory.decodeResource(mActivity.getResources(), R.drawable.ri_filetransfer_off, opt);
          mImageParamsDefault = new LayoutParams(opt.outWidth * 2, opt.outHeight * 2);
          bitmapCache = BitmapCache.getInstance();
          mSmileyResources = new Smileys(activity);
          mSingleChat = singleChat;
          mChatService = chatService;
          mFileTransferService = fileTransferService;
      }
  
      @Override
      public View newView(Context context, Cursor cursor, ViewGroup parent) {
          int viewType = getItemViewType(cursor);
          switch (viewType) {
              case VIEW_TYPE_RCS_CHAT_IN:
                  View view = mInflater.inflate(mSingleChat ? R.layout.talk_item_rcs_chat_in
                          : R.layout.gchat_item_rcs_chat_in, parent, false);
                  view.setTag(new RcsChatInViewHolder(view, cursor));
                  return view;
  
              case VIEW_TYPE_RCS_CHAT_OUT:
                  view = mInflater.inflate(mSingleChat ? R.layout.talk_item_rcs_chat_out
                          : R.layout.gchat_item_rcs_chat_out, parent, false);
                  view.setTag(new RcsChatOutViewHolder(view, cursor));
                  return view;
  
              case VIEW_TYPE_RCS_FILE_TRANSFER_IN:
                  view = mInflater.inflate(mSingleChat ? R.layout.talk_item_rcs_file_transfer_in
                          : R.layout.gchat_item_rcs_file_transfer_in, parent, false);
                  view.setTag(new RcsFileTransferInViewHolder(view, cursor));
                  return view;
  
              case VIEW_TYPE_RCS_FILE_TRANSFER_OUT:
                  view = mInflater.inflate(mSingleChat ? R.layout.talk_item_rcs_file_transfer_out
                          : R.layout.gchat_item_rcs_file_transfer_out, parent, false);
                  view.setTag(new RcsFileTransferOutViewHolder(view, cursor));
                  return view;
  
              case VIEW_TYPE_RCS_GROUP_CHAT_EVENT:
                  view = mInflater.inflate(R.layout.groupchat_event_view_item, parent, false);
                  view.setTag(new BasicViewHolder(view, cursor));
                  return view;
              default:
                  throw new IllegalArgumentException("Invalid view type: '" + viewType + "'!");
          }
      }
  
      private void bindRemoteContact(View view, Context ctx, Cursor cursor) {
          BasicViewHolder holder = (BasicViewHolder) view.getTag();
          String displayName = null;
          if (Direction.OUTGOING != Direction.valueOf(cursor.getInt(holder.getColumnDirectionIdx()))) {
              String number = cursor.getString(holder.getColumnContactIdx());
              if (number != null) {
                  ContactId contact = ContactUtil.formatContact(number);
                  if (mContactIdDisplayNameMap.containsKey(contact)) {
                      displayName = mContactIdDisplayNameMap.get(contact);
                  } else {
                      displayName = RcsContactUtil.getInstance(ctx).getDisplayName(contact);
                      mContactIdDisplayNameMap.put(contact, displayName);
                  }
              }
              holder.getContactText().setText(displayName);
          }
      }
  
      @Override
      public void bindView(View view, Context ctx, Cursor cursor) {
          if (!mSingleChat) {
              bindRemoteContact(view, ctx, cursor);
          }
          int viewType = getItemViewType(cursor);
          switch (viewType) {
              case VIEW_TYPE_RCS_CHAT_IN:
                  bindRcsChatInView(view, cursor);
                  break;
              case VIEW_TYPE_RCS_CHAT_OUT:
                  bindRcsChatOutView(view, cursor);
                  break;
              case VIEW_TYPE_RCS_FILE_TRANSFER_IN:
                  bindRcsFileTransferInView(view, cursor);
                  break;
              case VIEW_TYPE_RCS_FILE_TRANSFER_OUT:
                  bindRcsFileTransferOutView(view, cursor);
                  break;
              case VIEW_TYPE_RCS_GROUP_CHAT_EVENT:
                  bindRcsGroupChatEvent(view, cursor);
                  break;
              default:
                  throw new IllegalArgumentException("Invalid view type: '" + viewType + "'!");
          }
      }
  
      public int getItemViewType(Cursor cursor) {
          int providerId = cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.PROVIDER_ID));
          Direction direction = Direction.valueOf(cursor.getInt(cursor
                  .getColumnIndexOrThrow(HistoryLog.DIRECTION)));
          String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.MIME_TYPE));
          switch (providerId) {
              case ChatLog.Message.HISTORYLOG_MEMBER_ID:
                  switch (mimeType) {
                      case ChatLog.Message.MimeType.GROUPCHAT_EVENT:
                          return VIEW_TYPE_RCS_GROUP_CHAT_EVENT;
  
                      case ChatLog.Message.MimeType.GEOLOC_MESSAGE:
                      case ChatLog.Message.MimeType.TEXT_MESSAGE:
                          if (Direction.INCOMING == direction) {
                              return VIEW_TYPE_RCS_CHAT_IN;
                          }
                          return VIEW_TYPE_RCS_CHAT_OUT;
                  }
                  throw new IllegalArgumentException("Invalid mime type: '" + mimeType + "'!");
  
              case FileTransferLog.HISTORYLOG_MEMBER_ID:
                  if (Direction.INCOMING == direction) {
                      return VIEW_TYPE_RCS_FILE_TRANSFER_IN;
                  }
                  return VIEW_TYPE_RCS_FILE_TRANSFER_OUT;
          }
          throw new IllegalArgumentException("Invalid provider ID: '" + providerId + "'!");
      }
  
      @Override
      public int getItemViewType(int position) {
          return getItemViewType((Cursor) getItem(position));
      }
  
      @Override
      public int getViewTypeCount() {
          return 5;
      }
  
      private void bindRcsGroupChatEvent(View view, Cursor cursor) {
          BasicViewHolder holder = (BasicViewHolder) view.getTag();
          holder.getTimestampText().setText(
                  DateUtils.getRelativeTimeSpanString(cursor.getLong(holder.getColumnTimestampIdx()),
                          System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
                          DateUtils.FORMAT_ABBREV_RELATIVE));
          String event = RiApplication.sGroupChatEvents[cursor.getInt(holder.getColumnStatusIdx())];
          holder.getStatusText().setText(mActivity.getString(R.string.label_groupchat_event, event));
      }
  
      private void bindRcsFileTransferOutView(View view, Cursor cursor) {
          RcsFileTransferOutViewHolder holder = (RcsFileTransferOutViewHolder) view.getTag();
          holder.getTimestampText().setText(
                  DateUtils.getRelativeTimeSpanString(cursor.getLong(holder.getColumnTimestampIdx()),
                          System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
                          DateUtils.FORMAT_ABBREV_RELATIVE));
          String mimeType = cursor.getString(holder.getColumnMimetypeIdx());
          StringBuilder sb = new StringBuilder(cursor.getString(holder.getColumnFilenameIdx()));
          long filesize = cursor.getLong(holder.getColumnFilesizeIdx());
          long transferred = cursor.getLong(holder.getColumnTransferredIdx());
          final ImageView imageView = holder.getFileImageView();
          imageView.setOnClickListener(null);
          imageView.setLayoutParams(mImageParamsDefault);
          imageView.setImageResource(R.drawable.ri_filetransfer_on);
          if (filesize != transferred) {
              holder.getProgressText().setText(
                      sb.append(" : ").append(Utils.getProgressLabel(transferred, filesize))
                              .toString());
          } else {
              holder.getProgressText().setText(
                      sb.append(" (").append(FileUtils.humanReadableByteCount(filesize, true))
                              .append(")").toString());
          }
          final Uri file = Uri.parse(cursor.getString(holder.getColumnContentIdx()));
          if (Utils.isImageType(mimeType)) {
              String filePath = FileUtils.getPath(mContext, file);
              Bitmap imageBitmap = null;
              if (filePath != null) {
                  LruCache<String, BitmapCacheInfo> memoryCache = bitmapCache.getMemoryCache();
                  BitmapCacheInfo bitmapCacheInfo = memoryCache.get(filePath);
                  if (bitmapCacheInfo == null) {
                      ImageBitmapLoader loader = new ImageBitmapLoader(mContext, memoryCache,
                              MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT, new BitmapLoader.SetViewCallback() {
                                  @Override
                                  public void loadView(BitmapCacheInfo cacheInfo) {
                                      imageView.setImageBitmap(cacheInfo.getBitmap());
                                      imageView.setLayoutParams(mImageParams);
                                  }
                              });
                      loader.execute(filePath);
                  } else {
                      imageBitmap = bitmapCacheInfo.getBitmap();
                  }
                  if (imageBitmap != null) {
                      imageView.setImageBitmap(imageBitmap);
                      imageView.setLayoutParams(mImageParams);
                  }
                  imageView.setOnClickListener(new View.OnClickListener() {
  
                      @Override
                      public void onClick(View v) {
                          Utils.showPicture(mActivity, file);
                      }
                  });
              }
          } else if (Utils.isAudioType(mimeType)) {
              imageView.setImageResource(R.drawable.headphone);
              imageView.setOnClickListener(new View.OnClickListener() {
  
                  @Override
                  public void onClick(View v) {
                      Utils.playAudio(mActivity, file);
                  }
              });
          }
          holder.getStatusText().setText(getRcsFileTransferStatus(cursor, holder));
          boolean undeliveredExpiration = cursor.getInt(holder.getColumnExpiredDeliveryIdx()) == 1;
          holder.getStatusText().setCompoundDrawablesWithIntrinsicBounds(
                  undeliveredExpiration ? R.drawable.chat_view_undelivered : 0, 0, 0, 0);
      }
  
      private void bindRcsFileTransferInView(View view, Cursor cursor) {
          final RcsFileTransferInViewHolder holder = (RcsFileTransferInViewHolder) view.getTag();
          holder.getTimestampText().setText(
                  DateUtils.getRelativeTimeSpanString(cursor.getLong(holder.getColumnTimestampIdx()),
                          System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
                          DateUtils.FORMAT_ABBREV_RELATIVE));
          String mimeType = cursor.getString(holder.getColumnMimetypeIdx());
          StringBuilder sb = new StringBuilder(cursor.getString(holder.getColumnFilenameIdx()));
          long filesize = cursor.getLong(holder.getColumnFilesizeIdx());
          long transferred = cursor.getLong(holder.getColumnTransferredIdx());
          final ImageView imageView = holder.getFileImageView();
          imageView.setOnClickListener(null);
          imageView.setLayoutParams(mImageParamsDefault);
          imageView.setImageResource(R.drawable.ri_filetransfer_off);
          if (filesize != transferred) {
              holder.getProgressText().setText(
                      sb.append(" : ").append(Utils.getProgressLabel(transferred, filesize))
                              .toString());
          } else {
              imageView.setImageResource(R.drawable.ri_filetransfer_on);
              final Uri file = Uri.parse(cursor.getString(holder.getColumnContentIdx()));
              final RcsService.ReadStatus readStatus = RcsService.ReadStatus.valueOf(cursor
                      .getInt(holder.getColumnReadStatusIdx()));
              final String id = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.ID));
              if (Utils.isImageType(mimeType)) {
                  final String filePath = FileUtils.getPath(mContext, file);
                  if (filePath != null) {
                      LruCache<String, BitmapCacheInfo> memoryCache = bitmapCache.getMemoryCache();
                      BitmapCacheInfo bitmapCacheInfo = memoryCache.get(filePath);
                      if (bitmapCacheInfo == null) {
                          ImageBitmapLoader loader = new ImageBitmapLoader(mContext, memoryCache,
                                  MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT,
                                  new BitmapLoader.SetViewCallback() {
                                      @Override
                                      public void loadView(BitmapCacheInfo cacheInfo) {
                                          imageView.setImageBitmap(cacheInfo.getBitmap());
                                          imageView.setLayoutParams(mImageParams);
                                      }
                                  });
                          loader.execute(filePath);
                      } else {
                          Bitmap imageBitmap = bitmapCacheInfo.getBitmap();
                          imageView.setImageBitmap(imageBitmap);
                          imageView.setLayoutParams(mImageParams);
                      }
                      imageView.setOnClickListener(new View.OnClickListener() {
  
                          @Override
                          public void onClick(View v) {
                              Utils.showPicture(mActivity, file);
                              markFileTransferAsRead(id, readStatus);
                          }
                      });
                  }
              } else if (Utils.isAudioType(mimeType)) {
                  imageView.setImageResource(R.drawable.headphone);
                  imageView.setOnClickListener(new View.OnClickListener() {
  
                      @Override
                      public void onClick(View v) {
                          Utils.playAudio(mActivity, file);
                          markFileTransferAsRead(id, readStatus);
                      }
                  });
              }
              holder.getProgressText().setText(
                      sb.append(" (").append(FileUtils.humanReadableByteCount(filesize, true))
                              .append(")").toString());
          }
          holder.getStatusText().setText(getRcsFileTransferStatus(cursor, holder));
      }
  
      private void markFileTransferAsRead(String ftId, RcsService.ReadStatus readStatus) {
          try {
              if (RcsService.ReadStatus.UNREAD == readStatus) {
                  if (LogUtils.isActive) {
                      Log.d(LOGTAG, "Mark file transfer " + ftId + " as read");
                  }
                  mFileTransferService.markFileTransferAsRead(ftId);
              }
          } catch (RcsServiceNotAvailableException e) {
              if (LogUtils.isActive) {
                  Log.d(LOGTAG, "Cannot mark message as read: service not available");
              }
          } catch (RcsGenericException | RcsPersistentStorageException e) {
              Log.e(LOGTAG, ExceptionUtil.getFullStackTrace(e));
          }
      }
  
      private RcsChatInViewHolder bindRcsChatView(View view, Cursor cursor) {
          RcsChatInViewHolder holder = (RcsChatInViewHolder) view.getTag();
          holder.getTimestampText().setText(
                  DateUtils.getRelativeTimeSpanString(cursor.getLong(holder.getColumnTimestampIdx()),
                          System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
                          DateUtils.FORMAT_ABBREV_RELATIVE));
          String mimeType = cursor.getString(holder.getColumnMimetypeIdx());
          TextView contentText = holder.getContentText();
          String data = cursor.getString(holder.getColumnContentIdx());
          if (MimeType.TEXT_MESSAGE.equals(mimeType)) {
              contentText.setText(formatMessageWithSmiley(data));
          } else {
              contentText.setText(formatGeolocation(mContext, new Geoloc(data)));
          }
          holder.getStatusText().setText(getRcsChatStatus(cursor, holder));
          return holder;
      }
  
      private void bindRcsChatOutView(View view, Cursor cursor) {
          RcsChatOutViewHolder holder = (RcsChatOutViewHolder) bindRcsChatView(view, cursor);
          boolean undeliveredExpiration = cursor.getInt(holder.getColumnExpiredDeliveryIdx()) == 1;
          holder.getStatusText().setCompoundDrawablesWithIntrinsicBounds(
                  undeliveredExpiration ? R.drawable.chat_view_undelivered : 0, 0, 0, 0);
      }
  
      private void bindRcsChatInView(View view, Cursor cursor) {
          RcsChatInViewHolder holder = bindRcsChatView(view, cursor);
          // Only mark message as read when actually displayed on screen
          markChatMessageAsRead(cursor, holder);
      }
  
      private void markChatMessageAsRead(Cursor cursor, RcsChatInViewHolder holder) {
          try {
              RcsService.ReadStatus readStatus = RcsService.ReadStatus.valueOf(cursor.getInt(holder
                      .getColumnReadStatusIdx()));
              if (RcsService.ReadStatus.UNREAD == readStatus) {
                  String msgId = cursor.getString(holder.getColumnIdIdx());
                  if (LogUtils.isActive) {
                      Log.d(LOGTAG, "Mark message " + msgId + " as read");
                  }
                  mChatService.markMessageAsRead(msgId);
              }
          } catch (RcsServiceNotAvailableException e) {
              if (LogUtils.isActive) {
                  Log.d(LOGTAG, "Cannot mark message as read: service not available");
              }
          } catch (RcsGenericException | RcsPersistentStorageException e) {
              Log.e(LOGTAG, ExceptionUtil.getFullStackTrace(e));
          }
      }
  
      private String getRcsFileTransferStatus(Cursor cursor, RcsFileTransferInViewHolder holder) {
          FileTransfer.State state = FileTransfer.State.valueOf((int) cursor.getLong(holder
                  .getColumnStatusIdx()));
          StringBuilder status = new StringBuilder(RiApplication.sFileTransferStates[state.toInt()]);
          ReasonCode reason = ReasonCode
                  .valueOf((int) cursor.getLong(holder.getColumnReasonCodeIdx()));
          if (ReasonCode.UNSPECIFIED != reason) {
              status.append(" / ");
              status.append(RiApplication.sFileTransferReasonCodes[reason.toInt()]);
          }
          return status.toString();
      }
  
      private String getRcsChatStatus(Cursor cursor, RcsChatInViewHolder holder) {
          ChatLog.Message.Content.Status state = ChatLog.Message.Content.Status.valueOf((int) cursor
                  .getLong(holder.getColumnStatusIdx()));
          StringBuilder status = new StringBuilder(RiApplication.sMessagesStatuses[state.toInt()]);
          Content.ReasonCode reason = Content.ReasonCode.valueOf((int) cursor.getLong(holder
                  .getColumnReasonCodeIdx()));
          if (Content.ReasonCode.UNSPECIFIED != reason) {
              status.append(" / ");
              status.append(RiApplication.sMessageReasonCodes[reason.toInt()]);
          }
          return status.toString();
      }
  
      /**
       * Format geolocation
       *
       * @param context context
       * @param geoloc The geolocation
       * @return a formatted text
       */
      public static String formatGeolocation(Context context, Geoloc geoloc) {
          StringBuilder result = new StringBuilder(context.getString(R.string.label_geolocation_msg))
                  .append("\n");
          String label = geoloc.getLabel();
          if (label != null) {
              result.append(context.getString(R.string.label_location)).append(" ").append(label)
                      .append("\n");
          }
          return result.append(context.getString(R.string.label_latitude)).append(" ")
                  .append(geoloc.getLatitude()).append("\n")
                  .append(context.getString(R.string.label_longitude)).append(" ")
                  .append(geoloc.getLongitude()).append("\n")
                  .append(context.getString(R.string.label_accuracy)).append(" ")
                  .append(geoloc.getAccuracy()).toString();
      }
  
      private CharSequence formatMessageWithSmiley(String txt) {
          SpannableStringBuilder buf = new SpannableStringBuilder();
          if (!TextUtils.isEmpty(txt)) {
              SmileyParser smileyParser = new SmileyParser(txt, mSmileyResources);
              smileyParser.parse();
              buf.append(smileyParser.getSpannableString(mContext));
          }
          return buf;
      }
  
  }
  