// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/login/user_manager.h"

#include "app/resource_bundle.h"
#include "base/compiler_specific.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/nss_util.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/input_method_library.h"
#include "chrome/browser/chromeos/login/ownership_service.h"
#include "chrome/browser/chromeos/user_cros_settings_provider.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "gfx/codec/png_codec.h"
#include "grit/theme_resources.h"

namespace chromeos {

namespace {

// A vector pref of the users who have logged into the device.
const char kLoggedInUsers[] = "LoggedInUsers";
// A dictionary that maps usernames to file paths to their images.
const char kUserImages[] = "UserImages";

// Incognito user is represented by an empty string (since some code already
// depends on that and it's hard to figure out what).
const char kIncognitoUser[] = "";

// Special pathes to default user images.
const char* kDefaultImageNames[] = {
    "default:gray",
    "default:green",
    "default:blue",
    "default:yellow",
    "default:red",
};

// Resource IDs of default user images.
const int kDefaultImageResources[] = {
  IDR_LOGIN_DEFAULT_USER,
  IDR_LOGIN_DEFAULT_USER_1,
  IDR_LOGIN_DEFAULT_USER_2,
  IDR_LOGIN_DEFAULT_USER_3,
  IDR_LOGIN_DEFAULT_USER_4
};

// The one true UserManager.
static UserManager* user_manager_ = NULL;

// Stores path to the image in local state. Runs on UI thread.
void SavePathToLocalState(const std::string& username,
                          const std::string& image_path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  PrefService* local_state = g_browser_process->local_state();
  DictionaryValue* images =
      local_state->GetMutableDictionary(kUserImages);
  images->SetWithoutPathExpansion(username, new StringValue(image_path));
  DVLOG(1) << "Saving path to user image in Local State.";
  local_state->SavePersistentPrefs();
}

// Saves image to file with specified path. Runs on FILE thread.
// Posts task for saving image path to local state on UI thread.
void SaveImageToFile(const SkBitmap& image,
                     const FilePath& image_path,
                     const std::string& username) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  std::vector<unsigned char> encoded_image;
  if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, true, &encoded_image)) {
    LOG(ERROR) << "Failed to PNG encode the image.";
    return;
  }

  if (file_util::WriteFile(image_path,
                           reinterpret_cast<char*>(&encoded_image[0]),
                           encoded_image.size()) == -1) {
    LOG(ERROR) << "Failed to save image to file.";
    return;
  }

  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      NewRunnableFunction(&SavePathToLocalState,
                          username, image_path.value()));
}

// Deletes user's image file. Runs on FILE thread.
void DeleteUserImage(const FilePath& image_path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  if (!file_util::Delete(image_path, false)) {
    LOG(ERROR) << "Failed to remove user image.";
    return;
  }
}

// Checks if given path is one of the default ones. If it is, returns true
// and its index in kDefaultImageNames through |image_id|. If not, returns
// false.
bool IsDefaultImagePath(const std::string& path, size_t* image_id) {
  DCHECK(image_id);
  for (size_t i = 0; i < arraysize(kDefaultImageNames); ++i) {
    if (path == kDefaultImageNames[i]) {
      *image_id = i;
      return true;
    }
  }
  return false;
}

// Updates current user ownership on UI thread.
void UpdateOwnership(bool is_owner) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  UserManager* user_manager = UserManager::Get();
  user_manager->set_current_user_is_owner(is_owner);

  if (is_owner) {
    // Also update cached value.
    UserCrosSettingsProvider::UpdateCachedOwner(
      user_manager->logged_in_user().email());
  }
}

// Checks current user's ownership on file thread.
void CheckOwnership() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  bool is_owner = OwnershipService::GetSharedInstance()->CurrentUserIsOwner();

  // UserManager should be accessed only on UI thread.
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      NewRunnableFunction(&UpdateOwnership, is_owner));
}

}  // namespace

UserManager::User::User() {
  image_ = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
      IDR_LOGIN_DEFAULT_USER);
}

std::string UserManager::User::GetDisplayName() const {
  size_t i = email_.find('@');
  if (i == 0 || i == std::string::npos) {
    return email_;
  }
  return email_.substr(0, i);
}

// static
UserManager* UserManager::Get() {
  if (!user_manager_)
    user_manager_ = new UserManager();
  return user_manager_;
}

// static
void UserManager::RegisterPrefs(PrefService* local_state) {
  local_state->RegisterListPref(kLoggedInUsers);
  local_state->RegisterDictionaryPref(kUserImages);
}

std::vector<UserManager::User> UserManager::GetUsers() const {
  std::vector<User> users;
  if (!g_browser_process)
    return users;

  PrefService* local_state = g_browser_process->local_state();
  const ListValue* prefs_users = local_state->GetList(kLoggedInUsers);
  const DictionaryValue* prefs_images =
      local_state->GetMutableDictionary(kUserImages);

  if (prefs_users) {
    for (ListValue::const_iterator it = prefs_users->begin();
         it != prefs_users->end();
         ++it) {
      std::string email;
      if ((*it)->GetAsString(&email)) {
        User user;
        user.set_email(email);
        UserImages::const_iterator image_it = user_images_.find(email);
        std::string image_path;
        if (image_it == user_images_.end()) {
          if (prefs_images &&
              prefs_images->GetStringWithoutPathExpansion(email, &image_path)) {
            size_t default_image_id = arraysize(kDefaultImageNames);
            if (IsDefaultImagePath(image_path, &default_image_id)) {
              DCHECK(default_image_id < arraysize(kDefaultImageNames));
              int resource_id = kDefaultImageResources[default_image_id];
              user.set_image(
                  *ResourceBundle::GetSharedInstance().GetBitmapNamed(
                      resource_id));
              user_images_[email] = user.image();
            } else {
              // Insert the default image so we don't send another request if
              // GetUsers is called twice.
              user_images_[email] = user.image();
              image_loader_->Start(email, image_path);
            }
          }
        } else {
          user.set_image(image_it->second);
        }
        users.push_back(user);
      }
    }
  }
  return users;
}

void UserManager::OffTheRecordUserLoggedIn() {
  user_is_logged_in_ = true;
  logged_in_user_ = User();
  logged_in_user_.set_email(kIncognitoUser);
  NotifyOnLogin();
}

void UserManager::UserLoggedIn(const std::string& email) {
  if (email == kIncognitoUser) {
    OffTheRecordUserLoggedIn();
    return;
  }

  if (!IsKnownUser(email)) {
    current_user_is_new_ = true;
    browser_defaults::skip_restore = true;
  }

  // Get a copy of the current users.
  std::vector<User> users = GetUsers();

  // Clear the prefs view of the users.
  PrefService* prefs = g_browser_process->local_state();
  ListValue* prefs_users = prefs->GetMutableList(kLoggedInUsers);
  prefs_users->Clear();

  user_is_logged_in_ = true;
  logged_in_user_ = User();
  logged_in_user_.set_email(email);

  // Make sure this user is first.
  prefs_users->Append(Value::CreateStringValue(email));
  for (std::vector<User>::iterator it = users.begin();
       it != users.end();
       ++it) {
    std::string user_email = it->email();
    // Skip the most recent user.
    if (email != user_email) {
      prefs_users->Append(Value::CreateStringValue(user_email));
    } else {
      logged_in_user_ = *it;
    }
  }
  prefs->SavePersistentPrefs();
  NotifyOnLogin();
  if (current_user_is_new_)
    SetDefaultUserImage(email);
}

void UserManager::RemoveUser(const std::string& email) {
  // Get a copy of the current users.
  std::vector<User> users = GetUsers();

  // Clear the prefs view of the users.
  PrefService* prefs = g_browser_process->local_state();
  ListValue* prefs_users = prefs->GetMutableList(kLoggedInUsers);
  DCHECK(prefs_users);
  prefs_users->Clear();

  for (std::vector<User>::iterator it = users.begin();
       it != users.end();
       ++it) {
    std::string user_email = it->email();
    // Skip user that we would like to delete.
    if (email != user_email)
      prefs_users->Append(Value::CreateStringValue(user_email));
  }

  DictionaryValue* prefs_images = prefs->GetMutableDictionary(kUserImages);
  DCHECK(prefs_images);
  std::string image_path_string;
  prefs_images->GetStringWithoutPathExpansion(email, &image_path_string);
  prefs_images->RemoveWithoutPathExpansion(email, NULL);

  prefs->SavePersistentPrefs();

  size_t default_image_id;
  if (!IsDefaultImagePath(image_path_string, &default_image_id)) {
    FilePath image_path(image_path_string);
    BrowserThread::PostTask(
        BrowserThread::FILE,
        FROM_HERE,
        NewRunnableFunction(&DeleteUserImage,
                            image_path));
  }
}

bool UserManager::IsKnownUser(const std::string& email) {
  std::vector<User> users = GetUsers();
  for (std::vector<User>::iterator it = users.begin();
       it < users.end();
       ++it) {
    if (it->email() == email)
      return true;
  }

  return false;
}

void UserManager::SetLoggedInUserImage(const SkBitmap& image) {
  if (logged_in_user_.email().empty())
    return;
  logged_in_user_.set_image(image);
  OnImageLoaded(logged_in_user_.email(), image);
}

void UserManager::SaveUserImage(const std::string& username,
                                const SkBitmap& image) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  FilePath image_path = GetImagePathForUser(username);
  DVLOG(1) << "Saving user image to " << image_path.value();

  BrowserThread::PostTask(
      BrowserThread::FILE,
      FROM_HERE,
      NewRunnableFunction(&SaveImageToFile,
                          image, image_path, username));
}

void UserManager::SetDefaultUserImage(const std::string& username) {
  if (!g_browser_process)
    return;

  PrefService* local_state = g_browser_process->local_state();
  DCHECK(local_state);
  const ListValue* prefs_users = local_state->GetList(kLoggedInUsers);
  DCHECK(prefs_users);
  const DictionaryValue* prefs_images =
      local_state->GetDictionary(kUserImages);
  DCHECK(prefs_images);

  // We want to distribute default images between users uniformly so that if
  // there're more users with red image, we won't add red one for sure.
  // Thus we count how many default images of each color are used and choose
  // the first color with minimal usage.
  std::vector<int> colors_count(arraysize(kDefaultImageNames), 0);
  for (ListValue::const_iterator it = prefs_users->begin();
       it != prefs_users->end();
       ++it) {
    std::string email;
    if ((*it)->GetAsString(&email)) {
      std::string image_path;
      size_t default_image_id = arraysize(kDefaultImageNames);
      if (prefs_images->GetStringWithoutPathExpansion(email, &image_path) &&
          IsDefaultImagePath(image_path, &default_image_id)) {
        DCHECK(default_image_id < arraysize(kDefaultImageNames));
        ++colors_count[default_image_id];
      }
    }
  }
  std::vector<int>::const_iterator min_it =
      std::min_element(colors_count.begin(), colors_count.end());
  int selected_id = min_it - colors_count.begin();
  std::string user_image_path = kDefaultImageNames[selected_id];
  int resource_id = kDefaultImageResources[selected_id];
  SkBitmap user_image = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
      resource_id);

  SavePathToLocalState(username, user_image_path);
  SetLoggedInUserImage(user_image);
}

void UserManager::OnImageLoaded(const std::string& username,
                                const SkBitmap& image) {
  DVLOG(1) << "Loaded image for " << username;
  user_images_[username] = image;
  User user;
  user.set_email(username);
  user.set_image(image);
  NotificationService::current()->Notify(
      NotificationType::LOGIN_USER_IMAGE_CHANGED,
      Source<UserManager>(this),
      Details<const User>(&user));
}

// Private constructor and destructor. Do nothing.
UserManager::UserManager()
    : ALLOW_THIS_IN_INITIALIZER_LIST(image_loader_(new UserImageLoader(this))),
      current_user_is_owner_(false),
      current_user_is_new_(false),
      user_is_logged_in_(false) {
  registrar_.Add(this, NotificationType::OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED,
      NotificationService::AllSources());
}

UserManager::~UserManager() {
  image_loader_->set_delegate(NULL);
}

FilePath UserManager::GetImagePathForUser(const std::string& username) {
  std::string filename = username + ".png";
  FilePath user_data_dir;
  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
  return user_data_dir.AppendASCII(filename);
}

void UserManager::NotifyOnLogin() {
  NotificationService::current()->Notify(
      NotificationType::LOGIN_USER_CHANGED,
      Source<UserManager>(this),
      Details<const User>(&logged_in_user_));

  chromeos::CrosLibrary::Get()->GetInputMethodLibrary()->
      SetDeferImeStartup(false);
  // Shut down the IME so that it will reload the user's settings.
  chromeos::CrosLibrary::Get()->GetInputMethodLibrary()->
      StopInputMethodProcesses();
  // Let the window manager know that we're logged in now.
  WmIpc::instance()->SetLoggedInProperty(true);
  // Ensure we've opened the real user's key/certificate database.
  base::OpenPersistentNSSDB();

  // Schedules current user ownership check on file thread.
  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
      NewRunnableFunction(&CheckOwnership));
}

void UserManager::Observe(NotificationType type,
                          const NotificationSource& source,
                          const NotificationDetails& details) {
  if (type == NotificationType::OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED) {
    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
        NewRunnableFunction(&CheckOwnership));
  }
}

}  // namespace chromeos