// Copyright (c) 2023, Alliance for Open Media. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "avifinfo.h"

//------------------------------------------------------------------------------
// Simple API

// Returns true if the file is an AVIF file.
static int Identify(const char* file_path) {
  FILE* file = fopen(file_path, "rb");
  if (!file) return 0;
  uint8_t data[32];  // File type is likely known in the first 32 bytes.
  const size_t data_size = fread(data, 1, sizeof(data), file);
  fclose(file);
  if (AvifInfoIdentify(data, data_size) == kAvifInfoOk) {
    // The file signature identifies it as AVIF.
    return 1;
  }
  return 0;
}

// Returns true if the file is an AVIF file and if a few features were
// successfully parsed.
static int IdentifyAndGetFeatures(const char* file_path) {
  FILE* file = fopen(file_path, "rb");
  if (!file) return 0;
  uint8_t data[1024];  // Features are probably available within 1024 bytes.
  const size_t data_size = fread(data, 1, sizeof(data), file);
  fclose(file);
  AvifInfoFeatures features;
  if (AvifInfoGetFeatures(data, data_size, &features) == kAvifInfoOk) {
    // Use features.width, features.height etc.
    return 1;
  }
  return 0;
}

//------------------------------------------------------------------------------
// Stream API

typedef struct Stream {
  FILE* file;
  uint8_t data[AVIFINFO_MAX_NUM_READ_BYTES];
} Stream;

static const uint8_t* StreamRead(void* stream, size_t num_bytes) {
  Stream* s = (Stream*)stream;
  if (!s->file) return NULL;
  const size_t data_size = fread(s->data, 1, num_bytes, s->file);
  if (num_bytes > data_size) return NULL;
  return s->data;
}

static void StreamSkip(void* stream, size_t num_bytes) {
  Stream* s = (Stream*)stream;
  if (!s->file) return;
  if (fseek(s->file, num_bytes, SEEK_CUR)) {
    fclose(s->file);
    s->file = NULL;
  }
}

// Returns true if the file is an AVIF file.
static int IdentifyStream(const char* file_path) {
  Stream stream = {0};
  stream.file = fopen(file_path, "rb");
  if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) == kAvifInfoOk) {
    // The file signature identifies it as AVIF.
    fclose(stream.file);
    return 1;
  }
  fclose(stream.file);
  return 0;
}

// Returns true if the file is an AVIF file and if a few features were
// successfully parsed.
static int IdentifyAndGetFeaturesStream(const char* file_path) {
  Stream stream = {0};
  stream.file = fopen(file_path, "rb");
  if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) == kAvifInfoOk) {
    // The file signature identifies it as AVIF.

    AvifInfoFeatures features;
    if (AvifInfoGetFeaturesStream(&stream, StreamRead, StreamSkip, &features) ==
        kAvifInfoOk) {
      // Use features.width, features.height etc.
      fclose(stream.file);
      return 1;
    }
  }
  fclose(stream.file);
  return 0;
}

// Alternative to IdentifyAndGetFeaturesStream() where the stream object cannot
// be shared between AvifInfoIdentifyStream() and AvifInfoGetFeaturesStream().
static int IdentifyAndGetFeaturesStreams(const char* file_path) {
  {
    Stream stream = {0};
    stream.file = fopen(file_path, "rb");
    if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) ==
        kAvifInfoOk) {
      // The file signature identifies it as AVIF.
      fclose(stream.file);
      return 1;
    }
    fclose(stream.file);
  }
  {
    Stream stream = {0};
    stream.file = fopen(file_path, "rb");
    AvifInfoFeatures features;
    // This is allowed because AvifInfoIdentifyStream() was successful on the
    // same input file bytes.
    if (AvifInfoGetFeaturesStream(&stream, StreamRead, StreamSkip, &features) ==
        kAvifInfoOk) {
      // Use features.width, features.height etc.
      fclose(stream.file);
      return 1;
    }
    fclose(stream.file);
  }
  return 0;
}

//------------------------------------------------------------------------------

int main(int argc, char** argv) {
  if (argc < 2) {
    fprintf(stderr, "Usage: avifinfo_demo [file]...\n");
    return 1;
  }
  int res = 0;
  for (int i = 1; i < argc; ++i) {
    if (Identify(argv[i]) && IdentifyAndGetFeatures(argv[i]) &&
        IdentifyStream(argv[i]) && IdentifyAndGetFeaturesStream(argv[i]) &&
        IdentifyAndGetFeaturesStreams(argv[i])) {
      printf("%s is valid\n", argv[i]);
    } else {
      fprintf(stderr, "ERROR: %s is NOT a valid AVIF file\n", argv[1]);
      res = 1;
    }
  }
  return res;
}
