/*
 * 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 "pebble_process_md.h"

#include "process_loader.h"
#include "board/display.h"
#include <string.h>

//////////////////////
// Md Field Accessors
//////////////////////

const char* process_metadata_get_name(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->name;
  } else if (md->process_storage == ProcessStorageResource) {
    return ((const PebbleProcessMdResource*) md)->name;
  }
  return ((const PebbleProcessMdSystem*) md)->name;
}

uint32_t process_metadata_get_size_bytes(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->size_bytes;
  } else if (md->process_storage == ProcessStorageResource) {
    return ((const PebbleProcessMdResource*) md)->size_bytes;
  }
  return 0;
}

Version process_metadata_get_process_version(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->process_version;
  }
  return (Version) { 0, 0 };
}

Version process_metadata_get_sdk_version(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->sdk_version;
  }
  return (Version) { PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR, PROCESS_INFO_CURRENT_SDK_VERSION_MINOR };
}

ProcessAppRunLevel process_metadata_get_run_level(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ProcessAppRunLevelNormal;
  } else if (md->process_storage == ProcessStorageResource) {
    return ProcessAppRunLevelNormal;
  }
  return ((const PebbleProcessMdSystem*) md)->run_level;
}

int process_metadata_get_code_bank_num(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->code_bank_num;
  }
  return 0;
}

int process_metadata_get_res_bank_num(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->res_bank_num;
  }
  return 0;
}

ResourceVersion process_metadata_get_res_version(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->res_version;
  }
  return (ResourceVersion) { 0, 0 } ;
}

const uint8_t *process_metadata_get_build_id(const PebbleProcessMd *md) {
  if (md->process_storage == ProcessStorageFlash) {
    return ((const PebbleProcessMdFlash*) md)->build_id;
  }
  return NULL;
}

//////////////////////
// Md Builders
//////////////////////

static void prv_init_from_info_common(PebbleProcessMd *common, const PebbleProcessInfo *info,
      PebbleTask task, ProcessStorage process_storage) {

  memcpy(&common->uuid, &info->uuid, sizeof(Uuid));
  common->process_storage = process_storage;

  // Flags
  common->process_type = process_metadata_flags_process_type(info->flags, task);
  common->visibility = process_metadata_flags_visibility(info->flags);
  common->allow_js = process_metadata_flags_allow_js(info->flags);
  common->has_worker = process_metadata_flags_has_worker(info->flags);
  common->is_rocky_app = process_metadata_flags_rocky_app(info->flags);
  common->stored_sdk_platform = process_metadata_flags_stored_sdk_platform(info->flags);
  common->is_unprivileged = true;
  // We don't know the load address of the process until the process is
  // actually loaded, so we can't convert the entry point's offset into
  // an address until it's actually been loaded into that address.
  // Just shove the unmodified offset into the struct and let the
  // process loader convert it into an absolute address.
  // TODO: refactor the PebbleProcessMd stuff so that metadata that
  // isn't needed before load or can't be fully resolved until then
  // is left out of PebbleProcessMdCommon.
  common->main_func = (void *)(uintptr_t)info->offset;
}


void process_metadata_init_with_flash_header(PebbleProcessMdFlash *md, 
  const PebbleProcessInfo *info, int code_bank_num, PebbleTask task, uint8_t *build_id_buffer) {

  *md = (PebbleProcessMdFlash){};
  prv_init_from_info_common(&md->common, info, task, ProcessStorageFlash);

  // Flash app specific fields
  strncpy(md->name, info->name, sizeof(md->name));
  md->name[sizeof(md->name) - 1] = 0;

  md->size_bytes = info->virtual_size;

  md->process_version = info->process_version;
  md->sdk_version = info->sdk_version;

  md->code_bank_num = code_bank_num;
  md->res_bank_num = code_bank_num;

  md->res_version = (ResourceVersion) {
    .crc = info->resource_crc,
    .timestamp = info->resource_timestamp
  };

  if (build_id_buffer) {
    memcpy(md->build_id, build_id_buffer, BUILD_ID_EXPECTED_LEN);
  }
}

void process_metadata_init_with_resource_header(PebbleProcessMdResource *md,
    const PebbleProcessInfo *info, int bin_resource_id, PebbleTask task) {

  *md = (PebbleProcessMdResource){};
  prv_init_from_info_common(&md->common, info, task, ProcessStorageResource);

  // Resource app specific fields
  strncpy(md->name, info->name, sizeof(md->name));
  md->name[sizeof(md->name) - 1] = 0;

  md->size_bytes = info->virtual_size;
  md->bin_resource_id = bin_resource_id;
}

//////////////////////////////////
// PebbleProcessInfoFlags Helpers
//////////////////////////////////

ProcessVisibility process_metadata_flags_visibility(PebbleProcessInfoFlags flags) {
  if (flags & PROCESS_INFO_VISIBILITY_HIDDEN) {
    return ProcessVisibilityHidden;
  } else if (flags & PROCESS_INFO_VISIBILITY_SHOWN_ON_COMMUNICATION) {
    return ProcessVisibilityShownOnCommunication;
  } else {
    return ProcessVisibilityShown;
  }
}

ProcessType process_metadata_flags_process_type(PebbleProcessInfoFlags flags, PebbleTask task) {
  // Flags
  if (flags & PROCESS_INFO_WATCH_FACE) {
    return ProcessTypeWatchface;
  } else if (task == PebbleTask_Worker) {
    // BG_TODO: Set a bit in the PebbleProcessInfo to indicate it's a worker instead of having to
    // pass in the is_worker argument. Need to update process_metadata_get_flags_bitfield() to
    // match as well.
    return ProcessTypeWorker;
  } else {
    return ProcessTypeApp;
  }
}

bool process_metadata_flags_allow_js(PebbleProcessInfoFlags flags) {
  return ((flags & PROCESS_INFO_ALLOW_JS) != 0);
}

bool process_metadata_flags_has_worker(PebbleProcessInfoFlags flags) {
  return ((flags & PROCESS_INFO_HAS_WORKER) != 0);
}

bool process_metadata_flags_rocky_app(PebbleProcessInfoFlags flags) {
  return ((flags & PROCESS_INFO_ROCKY_APP) != 0);
}

uint16_t process_metadata_flags_stored_sdk_platform(PebbleProcessInfoFlags flags) {
  return (flags & PROCESS_INFO_PLATFORM_MASK);
}

static const Version first_3x_version = {
  PROCESS_INFO_FIRST_3X_SDK_VERSION_MAJOR,
  PROCESS_INFO_FIRST_3X_SDK_VERSION_MINOR,
};
static const Version first_4x_version = {
  PROCESS_INFO_FIRST_4X_SDK_VERSION_MAJOR,
  PROCESS_INFO_FIRST_4X_SDK_VERSION_MINOR,
};
static const Version first_4_2_version = {
  PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MAJOR,
  PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MINOR,
};


PlatformType process_metadata_get_app_sdk_platform(const PebbleProcessMd *md) {
  if (!md->is_unprivileged) {
    return PBL_PLATFORM_TYPE_CURRENT;
  }

  const Version app_sdk_version = process_metadata_get_sdk_version(md);

  // 2.0 <= SDK < 3.0
  if (version_compare(app_sdk_version, first_3x_version) < 0) {
    // 2.x SDKs didn't support anything but Aplite
    return PlatformTypeAplite;
  }
  // 3.0 <= SDK < 4.0
  if (version_compare(app_sdk_version, first_4x_version) < 0) {
    return PBL_PLATFORM_SWITCH(PBL_PLATFORM_TYPE_CURRENT,
         /* aplite */  PlatformTypeAplite, // unreachable, since we don't build for Tintin anymore
         /* basalt */  PlatformTypeBasalt,
         /* chalk */   PlatformTypeChalk,
         /* diorite */ PlatformTypeAplite, // there's was no Diorite SDK prior to 4.0
         /* emery */   PlatformTypeBasalt);
  }
  // 4.0 <= SDK < 4.2
  if (version_compare(app_sdk_version, first_4_2_version) < 0) {
    return PBL_PLATFORM_SWITCH(PBL_PLATFORM_TYPE_CURRENT,
        /* aplite */  PlatformTypeAplite, // unreachable, since we don't build for Tintin anymore
        /* basalt */  PlatformTypeBasalt,
        /* chalk */   PlatformTypeChalk,
        /* diorite */ PlatformTypeDiorite, // there's was no Aplite SDK after 4.0
        /* emery */   PlatformTypeBasalt);
  }

  // 4.2 <= SDK --> the flags should be filled correctly.
  const uint32_t stored_value = md->stored_sdk_platform;
  switch (stored_value) {
    case PROCESS_INFO_PLATFORM_APLITE:
      return PlatformTypeAplite;
    case PROCESS_INFO_PLATFORM_BASALT:
      return PlatformTypeBasalt;
    case PROCESS_INFO_PLATFORM_CHALK:
      return PlatformTypeChalk;
    case PROCESS_INFO_PLATFORM_DIORITE:
      return PlatformTypeDiorite;
    case PROCESS_INFO_PLATFORM_EMERY:
      return PlatformTypeEmery;
    default: {
      // If we encounter an unknown platform, we assume that it's meant for the current platform
      // (as it's most-likely a system-app). This is not a security risk as developers could always
      // patch the binaries as they wish anyway
      return PBL_PLATFORM_TYPE_CURRENT;
    }
  }
}

ProcessAppSDKType process_metadata_get_app_sdk_type(const PebbleProcessMd *md) {
  if (!md->is_unprivileged) {
    return ProcessAppSDKType_System;
  } else {
    const Version app_sdk_version = process_metadata_get_sdk_version(md);

    if (version_compare(app_sdk_version, first_3x_version) < 0) {
      return ProcessAppSDKType_Legacy2x;
    } else if (version_compare(app_sdk_version, first_4x_version) < 0) {
      return ProcessAppSDKType_Legacy3x;
    } else {
      return ProcessAppSDKType_4x;
    }
  }
}
