/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=79: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsMimeTypeArray.h"
#include "nsIScriptGlobalObject.h"
#include "nsIDOMNavigator.h"
#include "nsIDOMPluginArray.h"
#include "nsIDOMPlugin.h"
#include "nsDOMClassInfoID.h"
#include "nsIMIMEService.h"
#include "nsIMIMEInfo.h"
#include "nsIFile.h"


nsMimeTypeArray::nsMimeTypeArray(nsIDOMNavigator* navigator)
  : mNavigator(navigator),
    mPluginMimeTypeCount(0),
    mInited(false)
{
}

nsMimeTypeArray::~nsMimeTypeArray()
{
  Clear();
}


DOMCI_DATA(MimeTypeArray, nsMimeTypeArray)

// QueryInterface implementation for nsMimeTypeArray
NS_INTERFACE_MAP_BEGIN(nsMimeTypeArray)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
  NS_INTERFACE_MAP_ENTRY(nsIDOMMimeTypeArray)
  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MimeTypeArray)
NS_INTERFACE_MAP_END


NS_IMPL_ADDREF(nsMimeTypeArray)
NS_IMPL_RELEASE(nsMimeTypeArray)


NS_IMETHODIMP
nsMimeTypeArray::GetLength(uint32_t* aLength)
{
  if (!mInited) {
    nsresult rv = GetMimeTypes();
    if (rv != NS_OK)
      return rv;
  }

  NS_ASSERTION(mPluginMimeTypeCount <= (uint32_t)mMimeTypeArray.Count(),
               "The number of total mimetypes should be equal to or higher "
               "than the number of plugin mimetypes.");
 
  *aLength = mPluginMimeTypeCount;
  return NS_OK;
}

nsIDOMMimeType*
nsMimeTypeArray::GetItemAt(uint32_t aIndex, nsresult *aResult)
{
  if (!mInited) {
    *aResult = GetMimeTypes();
    if (*aResult != NS_OK)
      return nullptr;
  }

  NS_ASSERTION(mPluginMimeTypeCount <= (uint32_t)mMimeTypeArray.Count(),
               "The number of total mimetypes should be equal to or higher "
               "than the number of plugin mimetypes.");

  if (aIndex >= mPluginMimeTypeCount) {
    *aResult = NS_ERROR_FAILURE;

    return nullptr;
  }

  *aResult = NS_OK;

  return mMimeTypeArray[aIndex];
}

NS_IMETHODIMP
nsMimeTypeArray::Item(uint32_t aIndex, nsIDOMMimeType** aReturn)
{
  nsresult rv;

  NS_IF_ADDREF(*aReturn = GetItemAt(aIndex, &rv));

  return rv;
}

nsIDOMMimeType*
nsMimeTypeArray::GetNamedItem(const nsAString& aName, nsresult* aResult)
{
  if (!mInited) {
    *aResult = GetMimeTypes();
    if (*aResult != NS_OK)
      return nullptr;
  }

  NS_ASSERTION(mPluginMimeTypeCount <= (uint32_t)mMimeTypeArray.Count(),
               "The number of total mimetypes should be equal to or higher "
               "than the number of plugin mimetypes.");

  *aResult = NS_OK;

  nsAutoString type;

  for (int32_t i = 0; i < mMimeTypeArray.Count(); i++) {
    nsIDOMMimeType *mtype = mMimeTypeArray[i];

    mtype->GetType(type);

    if (type.Equals(aName)) {
      return mtype;
    }
  }

  // Now let's check with the MIME service.
  nsCOMPtr<nsIMIMEService> mimeSrv = do_GetService("@mozilla.org/mime;1");
  if (mimeSrv) {
    nsCOMPtr<nsIMIMEInfo> mimeInfo;
    mimeSrv->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(aName), EmptyCString(),
                                     getter_AddRefs(mimeInfo));
    if (mimeInfo) {
      // Now we check whether we can really claim to support this type
      nsHandlerInfoAction action = nsIHandlerInfo::saveToDisk;
      mimeInfo->GetPreferredAction(&action);
      if (action != nsIMIMEInfo::handleInternally) {
        bool hasHelper = false;
        mimeInfo->GetHasDefaultHandler(&hasHelper);
        if (!hasHelper) {
          nsCOMPtr<nsIHandlerApp> helper;
          mimeInfo->GetPreferredApplicationHandler(getter_AddRefs(helper));
          if (!helper) {
            // mime info from the OS may not have a PreferredApplicationHandler
            // so just check for an empty default description
            nsAutoString defaultDescription;
            mimeInfo->GetDefaultDescription(defaultDescription);
            if (defaultDescription.IsEmpty()) {
              // no support; just leave
              return nullptr;
            }
          }
        }
      }

      // If we got here, we support this type!  Say so.
      nsCOMPtr<nsIDOMMimeType> helper, entry;
      if (!(helper = new nsHelperMimeType(aName)) ||
          !(entry = new nsMimeType(nullptr, helper)) ||
          !mMimeTypeArray.AppendObject(entry)) {
        *aResult = NS_ERROR_OUT_OF_MEMORY;

        return nullptr;
      }

      return entry;
    }
  }

  return nullptr;
}

NS_IMETHODIMP
nsMimeTypeArray::NamedItem(const nsAString& aName, nsIDOMMimeType** aReturn)
{
  nsresult rv;

  NS_IF_ADDREF(*aReturn = GetNamedItem(aName, &rv));

  return rv;
}

void
nsMimeTypeArray::Clear()
{
  mInited = false;
  mMimeTypeArray.Clear();
  mPluginMimeTypeCount = 0;
}

void
nsMimeTypeArray::Refresh()
{
  Clear();
  GetMimeTypes();
}

nsresult
nsMimeTypeArray::GetMimeTypes()
{
  NS_PRECONDITION(!mInited && mPluginMimeTypeCount==0,
                      "already initialized");

  if (!mNavigator) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsIDOMPluginArray* pluginArray = nullptr;
  nsresult rv = mNavigator->GetPlugins(&pluginArray);
  if (rv == NS_OK) {
    // count up all possible MimeTypes, and collect them here. Later,
    // we'll remove duplicates.
    uint32_t pluginMimeTypeCount = 0;
    uint32_t pluginCount = 0;
    rv = pluginArray->GetLength(&pluginCount);
    if (rv == NS_OK) {
      uint32_t i;
      for (i = 0; i < pluginCount; i++) {
        nsCOMPtr<nsIDOMPlugin> plugin;
        if (NS_SUCCEEDED(pluginArray->Item(i, getter_AddRefs(plugin))) &&
            plugin) {
          uint32_t mimeTypeCount = 0;
          if (plugin->GetLength(&mimeTypeCount) == NS_OK)
            pluginMimeTypeCount += mimeTypeCount;
        }
      }
      // now we know how many there are, start gathering them.
      if (!mMimeTypeArray.SetCapacity(pluginMimeTypeCount))
        return NS_ERROR_OUT_OF_MEMORY;

      mPluginMimeTypeCount = pluginMimeTypeCount;
      mInited = true;

      uint32_t k;
      for (k = 0; k < pluginCount; k++) {
        nsCOMPtr<nsIDOMPlugin> plugin;
        if (NS_SUCCEEDED(pluginArray->Item(k, getter_AddRefs(plugin))) &&
            plugin) {
          uint32_t mimeTypeCount = 0;
          if (plugin->GetLength(&mimeTypeCount) == NS_OK) {
            nsCOMPtr<nsIDOMMimeType> item;
            for (uint32_t j = 0; j < mimeTypeCount; j++) {
              plugin->Item(j, getter_AddRefs(item));
              mMimeTypeArray.AppendObject(item);
            }
          }
        }
      }
    }
    NS_RELEASE(pluginArray);
  }
  return rv;
}

nsMimeType::nsMimeType(nsIDOMPlugin* aPlugin, nsIDOMMimeType* aMimeType)
{
  mPlugin = aPlugin;
  mMimeType = aMimeType;
}

nsMimeType::~nsMimeType()
{
}


DOMCI_DATA(MimeType, nsMimeType)

// QueryInterface implementation for nsMimeType
NS_INTERFACE_MAP_BEGIN(nsMimeType)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
  NS_INTERFACE_MAP_ENTRY(nsIDOMMimeType)
  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MimeType)
NS_INTERFACE_MAP_END


NS_IMPL_ADDREF(nsMimeType)
NS_IMPL_RELEASE(nsMimeType)


NS_IMETHODIMP
nsMimeType::GetDescription(nsAString& aDescription)
{
  return mMimeType->GetDescription(aDescription);
}

NS_IMETHODIMP
nsMimeType::GetEnabledPlugin(nsIDOMPlugin** aEnabledPlugin)
{
  nsAutoString type;
  GetType(type);

  *aEnabledPlugin = mPlugin;

  NS_IF_ADDREF(*aEnabledPlugin);

  return NS_OK;
}

NS_IMETHODIMP
nsMimeType::GetSuffixes(nsAString& aSuffixes)
{
  return mMimeType->GetSuffixes(aSuffixes);
}

NS_IMETHODIMP
nsMimeType::GetType(nsAString& aType)
{
  return mMimeType->GetType(aType);
}

// QueryInterface implementation for nsHelperMimeType
NS_IMPL_ISUPPORTS1(nsHelperMimeType, nsIDOMMimeType)

NS_IMETHODIMP
nsHelperMimeType::GetDescription(nsAString& aDescription)
{
  aDescription.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
nsHelperMimeType::GetEnabledPlugin(nsIDOMPlugin** aEnabledPlugin)
{
  *aEnabledPlugin = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsHelperMimeType::GetSuffixes(nsAString& aSuffixes)
{
  aSuffixes.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
nsHelperMimeType::GetType(nsAString& aType)
{
  aType = mType;
  return NS_OK;
}

