/*
 * Copyright 2024 Google LLC
 *
 * 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.
 */

#include "app_glance.h"

#include "applib/template_string.h"
#include "apps/system_apps/launcher/default/launcher_app_glance_generic.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "services/normal/blob_db/app_glance_db_private.h"
#include "services/normal/timeline/attribute.h"
#include "services/normal/timeline/timeline_resources.h"
#include "syscall/syscall.h"
#include "util/string.h"
#include "util/uuid.h"

AppGlanceResult app_glance_add_slice(AppGlanceReloadSession *session, AppGlanceSlice slice) {
  #if !CAPABILITY_HAS_APP_GLANCES
    return APP_GLANCE_RESULT_INVALID_SESSION;
  #endif
  AppGlanceResult result = APP_GLANCE_RESULT_SUCCESS;

  AppGlance *glance = app_state_get_glance();

  // We use the glance set in the session as a crude way of validating the session
  // Bail out if the session is invalid
  if (!session || (glance != session->glance)) {
    result |= APP_GLANCE_RESULT_INVALID_SESSION;
    return result;
  }

  // From here on to the end of the function we can accumulate multiple failures in `result`

  // Check if this slice would put us over the max slices per glance
  if (glance->num_slices >= APP_GLANCE_DB_MAX_SLICES_PER_GLANCE) {
    result |= APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED;
  }

  // Check if this slice's icon is a valid resource (but only if it's not the value that specifies
  // that the app's default icon should be used)
  if (slice.layout.icon != APP_GLANCE_SLICE_DEFAULT_ICON) {
    Uuid app_uuid;
    sys_get_app_uuid(&app_uuid);
    const TimelineResourceInfo timeline_resource_info = (TimelineResourceInfo) {
      .app_id = &app_uuid,
      .res_id = (TimelineResourceId)slice.layout.icon,
    };
    AppResourceInfo app_resource_info = (AppResourceInfo) {};
    sys_timeline_resources_get_id(&timeline_resource_info,
                                  LAUNCHER_APP_GLANCE_GENERIC_ICON_SIZE_TYPE, &app_resource_info);
    if (app_resource_info.res_id == RESOURCE_ID_INVALID) {
      result |= APP_GLANCE_RESULT_INVALID_ICON;
    }
  }

  // Check if the template string is too long (if it's not NULL)
  // Plus one for the null terminator, then plus one more to become too long
  const size_t template_string_too_long_size = ATTRIBUTE_APP_GLANCE_SUBTITLE_MAX_LEN + 1 + 1;
  if (slice.layout.subtitle_template_string &&
      strnlen(slice.layout.subtitle_template_string,
              template_string_too_long_size) == template_string_too_long_size) {
    result |= APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG;
  }

  // Check if the provided subtitle string is a valid template string, if it's present.
  const time_t current_time = sys_get_time();
  if (slice.layout.subtitle_template_string) {
    const TemplateStringVars template_string_vars = (TemplateStringVars) {
      .current_time = current_time,
    };
    TemplateStringError template_string_error = {0};
    if (!template_string_evaluate(slice.layout.subtitle_template_string, NULL, 0, NULL,
                                  &template_string_vars, &template_string_error)) {
      result |= APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING;
    }
  }

  // Check if the slice would expire in the past (if it's not APP_GLANCE_SLICE_NO_EXPIRATION)
  if ((slice.expiration_time != APP_GLANCE_SLICE_NO_EXPIRATION) &&
      slice.expiration_time <= current_time) {
    result |= APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST;
  }

  // If we haven't failed at this point, we're ready to add the slice to the glance!
  if (result == APP_GLANCE_RESULT_SUCCESS) {
    AppGlanceSliceInternal *slice_dest = &glance->slices[glance->num_slices];
    *slice_dest = (AppGlanceSliceInternal) {
      .expiration_time = slice.expiration_time,
      .type = AppGlanceSliceType_IconAndSubtitle,
      .icon_and_subtitle.icon_resource_id = slice.layout.icon,
    };
    // We already checked that the provided template string fits, so use strcpy instead of strncpy
    if (slice.layout.subtitle_template_string) {
      strcpy(slice_dest->icon_and_subtitle.template_string, slice.layout.subtitle_template_string);
    }
    glance->num_slices++;
  }

  return result;
}

void app_glance_reload(AppGlanceReloadCallback callback, void *context) {
  #if !CAPABILITY_HAS_APP_GLANCES
    return;
  #endif

  AppGlance *glance = app_state_get_glance();

  Uuid current_app_uuid;
  sys_get_app_uuid(&current_app_uuid);

  // Zero out the glance
  app_glance_service_init_glance(glance);

  if (callback) {
    // Create a "reload session" on the stack that wraps the glance, and then call the user callback
    AppGlanceReloadSession session = (AppGlanceReloadSession) {
      .glance = glance,
    };
    callback(&session, APP_GLANCE_DB_MAX_SLICES_PER_GLANCE, context);
  }

  sys_app_glance_update(&current_app_uuid, glance);
}
