/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.

  Keepass2Android is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 2 of the License, or
  (at your option) any later version.

  Keepass2Android 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 Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.
  */
using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Database;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;

using KeePassLib;
using KeePassLib.Utility;
using System.Threading;
using System.Collections.Generic;

namespace keepass2android.search
{
	[ContentProvider(new [] { SearchProvider.Authority })]
	public class SearchProvider : ContentProvider
	{
		private enum UriMatches
		{
			NoMatch = UriMatcher.NoMatch,
			GetIcon,
			GetSuggestions
		}
		public const String Authority = "keepass2android." + AppNames.PackagePart + ".SearchProvider";
		
		private const string GetIconPathQuery = "get_icon";
		private const string IconIdParameter = "IconId";
		private const string CustomIconUuidParameter = "CustomIconUuid";

		private Database _db;

		private static UriMatcher UriMatcher = BuildUriMatcher();

		static UriMatcher BuildUriMatcher()
		{
			var matcher = new UriMatcher(UriMatcher.NoMatch);

			// to get definitions...
			matcher.AddURI(Authority, GetIconPathQuery, (int)UriMatches.GetIcon);
			matcher.AddURI(Authority, SearchManager.SuggestUriPathQuery, (int)UriMatches.GetSuggestions);

			return matcher;
		}

		public override bool OnCreate()
		{
			_db = App.Kp2a.GetDb();
			return true;
		}

		public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
		{
			if (App.Kp2a.DatabaseIsUnlocked) // Can't show suggestions if the database is locked!
			{
				switch ((UriMatches)UriMatcher.Match(uri))
				{
					case UriMatches.GetSuggestions:
						var searchString = selectionArgs[0];
						if (!String.IsNullOrEmpty(searchString))
						{
							try
							{
								var resultsContexts = new Dictionary<PwUuid, String>();
								var result = _db.Search(new SearchParameters { SearchString = searchString }, resultsContexts );
								return new GroupCursor(result, resultsContexts);
							}
							catch (Exception e)
							{
								Kp2aLog.Log("Failed to search for suggestions: " + e.Message);
							}
						}
						break;
					case UriMatches.GetIcon:
						return null; // This will be handled by OpenAssetFile

					default:
						return null;
						//throw new ArgumentException("Unknown Uri: " + uri, "uri");
				}
			}

			return null;
		}

		public override ParcelFileDescriptor OpenFile(Android.Net.Uri uri, string mode)
		{
			switch ((UriMatches)UriMatcher.Match(uri))
			{
				case UriMatches.GetIcon:
					var iconId = (PwIcon)Enum.Parse(typeof(PwIcon), uri.GetQueryParameter(IconIdParameter));
					var customIconUuid = new PwUuid(MemUtil.HexStringToByteArray(uri.GetQueryParameter(CustomIconUuidParameter)));

					var iconDrawable = _db.DrawableFactory.GetIconDrawable(App.Context.Resources, _db.KpDatabase, iconId, customIconUuid) as BitmapDrawable;
					if (iconDrawable != null)
					{
						var pipe = ParcelFileDescriptor.CreatePipe();
						var outStream = new OutputStreamInvoker(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));

						ThreadPool.QueueUserWorkItem(state =>
							{
								iconDrawable.Bitmap.Compress(Bitmap.CompressFormat.Png, 100, outStream);
								outStream.Close();
							});
						
						return pipe[0];
					}

					// Couldn't get an icon for some reason.
					return null;
				default:
					throw new ArgumentException("Unknown Uri: " + uri, "uri");
			}
		}

		public override string GetType(Android.Net.Uri uri)
		{
			switch ((UriMatches)UriMatcher.Match(uri))
			{
				case UriMatches.GetSuggestions:
					return SearchManager.SuggestMimeType;
				case UriMatches.GetIcon:
					return "image/png";

				default:
					throw new ArgumentException("Unknown Uri: " + uri, "uri");
			}
		}

		#region Unimplemented
		public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
		{
			throw new NotImplementedException();
		}
		public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
		{
			throw new NotImplementedException();
		}
		public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
		{
			throw new NotImplementedException();
		}
		#endregion


		private class GroupCursor : AbstractCursor
		{
			private static readonly string[] ColumnNames = new[] { Android.Provider.BaseColumns.Id, 
																	SearchManager.SuggestColumnText1, 
																	SearchManager.SuggestColumnText2, 
																	SearchManager.SuggestColumnIcon1,
																	SearchManager.SuggestColumnIntentDataId,
			};

			private readonly PwGroup mGroup;
			private readonly IDictionary<PwUuid, String> mResultContexts;

			public GroupCursor(PwGroup group, IDictionary<PwUuid, String> resultContexts)
			{
				System.Diagnostics.Debug.Assert(!group.Groups.Any(), "Expecting a flat list of groups");

				mGroup = group;
				mResultContexts = resultContexts;
			}

			public override int Count
			{
				get { return (int)Math.Min(mGroup.GetEntriesCount(false), int.MaxValue); }
			}

			public override string[] GetColumnNames()
			{
				return ColumnNames;
			}

			public override FieldType GetType(int column)
			{
				switch (column)
				{
					case 0: // _ID
						return FieldType.Integer;
					default:
						return base.GetType(column); // Ends up as string
				}
			}

			private PwEntry CurrentEntry
			{
				get
				{
					return mGroup.Entries.GetAt((uint)MPos);
				}
			}

			public override long GetLong(int column)
			{
				switch (column)
				{
					case 0: // _ID
						return MPos;
					default:
						throw new FormatException();
				}
			}

			public override string GetString(int column)
			{
				switch (column)
				{
					case 0: // _ID
						return MPos.ToString();
					case 1: // SuggestColumnText1
						return CurrentEntry.Strings.ReadSafe(PwDefs.TitleField);
					case 2: // SuggestColumnText2
						string context;
						if (mResultContexts.TryGetValue(CurrentEntry.Uuid, out context))
						{
							context = Internationalise(context);
							return context;
						}
						return null;
					case 3: // SuggestColumnIcon1
						var builder = new Android.Net.Uri.Builder();
						builder.Scheme(ContentResolver.SchemeContent);
						builder.Authority(Authority);
						builder.Path(GetIconPathQuery);
						builder.AppendQueryParameter(IconIdParameter, CurrentEntry.IconId.ToString());
						builder.AppendQueryParameter(CustomIconUuidParameter, CurrentEntry.CustomIconUuid.ToHexString());
						return builder.Build().ToString();
					case 4: // SuggestColumnIntentDataId
						return CurrentEntry.Uuid.ToHexString();
					default:
						return null;
				}
			}

			private string Internationalise(string context)
			{
				// Some context names can be internationalised.
				var splitPos = context.IndexOf(':');
				var rawName = context.Substring(0, splitPos);
				int intlResourceId = 0;
				switch (rawName)
				{
					case PwDefs.TitleField:
						// We will already be showing Title, so ignore it entirely so it doesn't double-appear
						return null;
					case PwDefs.UserNameField:
						intlResourceId = Resource.String.entry_user_name;
						break;
					case PwDefs.UrlField:
						intlResourceId = Resource.String.entry_url;
						break;
					case PwDefs.NotesField:
						intlResourceId = Resource.String.entry_comment;
						break;
					case PwGroup.SearchContextTags:
						intlResourceId = Resource.String.entry_tags;
						break;
					default:
						// Other fields aren't part of the default SearchParameters, so we won't ever get them as context anyway
						break;
				}

				if (intlResourceId > 0)
				{
					return App.Context.GetString(intlResourceId) + context.Substring(splitPos);
				}

				return context;
			}

			public override bool IsNull(int column)
			{
				return false;
			}

			#region Data types appearing in no columns
			public override int GetInt(int column) { throw new FormatException(); }
			public override double GetDouble(int column) { throw new FormatException(); }
			public override float GetFloat(int column) { throw new FormatException(); }
			public override short GetShort(int column) { throw new FormatException(); }
			#endregion
		}

	}
}

