#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#include "src/webp/decode.h"
#include "src/webp/encode.h"
#include "src/webp/types.h"

static uint8_t *result_ptr = NULL;
static size_t result_len = 0;
static int result_width = 0;
static int result_height = 0;

__attribute__((visibility("default"))) void *sinter_webp_malloc(size_t size) {
  return malloc(size);
}

__attribute__((visibility("default"))) void sinter_webp_free(void *ptr) {
  free(ptr);
}

__attribute__((visibility("default"))) void sinter_webp_release_result(void) {
  if (result_ptr != NULL) {
    WebPFree(result_ptr);
  }
  result_ptr = NULL;
  result_len = 0;
  result_width = 0;
  result_height = 0;
}

__attribute__((visibility("default"))) const uint8_t *sinter_webp_result_ptr(void) {
  return result_ptr;
}

__attribute__((visibility("default"))) size_t sinter_webp_result_len(void) {
  return result_len;
}

__attribute__((visibility("default"))) uint32_t sinter_webp_result_width(void) {
  return (uint32_t)result_width;
}

__attribute__((visibility("default"))) uint32_t sinter_webp_result_height(void) {
  return (uint32_t)result_height;
}

__attribute__((visibility("default"))) int sinter_webp_decode(
  const uint8_t *input,
  size_t input_len
) {
  sinter_webp_release_result();
  if (input == NULL || input_len == 0) {
    return 0;
  }

  uint8_t *rgba = WebPDecodeRGBA(input, input_len, &result_width, &result_height);
  if (rgba == NULL || result_width <= 0 || result_height <= 0) {
    return 0;
  }

  result_ptr = rgba;
  result_len = (size_t)result_width * (size_t)result_height * 4;
  return 1;
}

__attribute__((visibility("default"))) int sinter_webp_encode(
  const uint8_t *rgba,
  size_t rgba_len,
  uint32_t width,
  uint32_t height,
  float quality,
  int lossless
) {
  sinter_webp_release_result();
  if (rgba == NULL || width == 0 || height == 0) {
    return 0;
  }

  size_t expected_len = (size_t)width * (size_t)height * 4;
  if (rgba_len != expected_len) {
    return 0;
  }

  uint8_t *out = NULL;
  size_t len = lossless
    ? WebPEncodeLosslessRGBA(rgba, (int)width, (int)height, (int)width * 4, &out)
    : WebPEncodeRGBA(rgba, (int)width, (int)height, (int)width * 4, quality, &out);
  if (len == 0 || out == NULL) {
    return 0;
  }

  result_ptr = out;
  result_len = len;
  result_width = (int)width;
  result_height = (int)height;
  return 1;
}

#include <string.h>
#include "src/webp/demux.h"
#include "src/webp/mux.h"
typedef struct { WebPAnimDecoder* decoder; WebPAnimInfo info; uint8_t* input; uint8_t* frame; int timestamp; int duration; int animated; } Reader;
typedef struct { WebPMux* mux; WebPData out; int width,height,count,added,lossless; float quality; int animated; } Writer;
void* reader_open(const uint8_t* input, size_t len, int kind) {
  (void)kind; Reader* r=calloc(1,sizeof(*r)); if(!r)return NULL;
  r->input=malloc(len); if(!r->input){free(r);return NULL;} memcpy(r->input,input,len);
  WebPData data={r->input,len};WebPDemuxer* dm=WebPDemux(&data);if(dm){r->animated=(WebPDemuxGetI(dm,WEBP_FF_FORMAT_FLAGS)&ANIMATION_FLAG)!=0;WebPDemuxDelete(dm);} WebPAnimDecoderOptions options; WebPAnimDecoderOptionsInit(&options); options.color_mode=MODE_RGBA; options.use_threads=0;
  r->decoder=WebPAnimDecoderNew(&data,&options);
  if(!r->decoder || !WebPAnimDecoderGetInfo(r->decoder,&r->info)){WebPAnimDecoderDelete(r->decoder);free(r->input);free(r);return NULL;} return r;
}
void reader_close(Reader* r){if(r){WebPAnimDecoderDelete(r->decoder);free(r->input);free(r);}}
uint32_t reader_width(Reader*r){return r->info.canvas_width;}
uint32_t reader_height(Reader*r){return r->info.canvas_height;}
uint32_t reader_count(Reader*r){return r->info.frame_count;}
uint32_t reader_plays(Reader*r){return r->animated?r->info.loop_count:1;}
int reader_next(Reader*r){if(!WebPAnimDecoderHasMoreFrames(r->decoder))return 0;int t;if(!WebPAnimDecoderGetNext(r->decoder,&r->frame,&t))return -1;r->duration=t-r->timestamp;r->timestamp=t;return 1;}
uint8_t* reader_data(Reader*r){return r->frame;}
double reader_duration(Reader*r){return (double)r->duration*1000;}
void* writer_open(int kind,int w,int h,int count,int plays,int quality,int lossless,int speed,int animated,double timebase){
 (void)kind;(void)speed;(void)timebase;if(w<=0||h<=0||count<=0||plays<0||plays>65535)return NULL;Writer*s=calloc(1,sizeof(*s));if(!s)return NULL;
 s->mux=WebPMuxNew();s->width=w;s->height=h;s->count=count;s->quality=quality;s->lossless=lossless;s->animated=animated;
 WebPMuxAnimParams p={0,plays};if(!s->mux||WebPMuxSetAnimationParams(s->mux,&p)!=WEBP_MUX_OK){WebPMuxDelete(s->mux);free(s);return NULL;}return s;
}
int writer_add(Writer*s,const uint8_t*rgba,size_t len,int duration,int denominator){
 (void)denominator;if(len!=(size_t)s->width*s->height*4||s->added>=s->count||duration<0||duration>16777215)return 0;
 uint8_t*out=NULL;size_t n=s->lossless?WebPEncodeLosslessRGBA(rgba,s->width,s->height,s->width*4,&out):WebPEncodeRGBA(rgba,s->width,s->height,s->width*4,s->quality,&out);if(!n)return 0;
 WebPData data={out,n};WebPMuxError err;
 if(!s->animated)err=WebPMuxSetImage(s->mux,&data,1);
 else{WebPMuxFrameInfo frame;memset(&frame,0,sizeof(frame));frame.bitstream=data;frame.duration=duration;frame.id=WEBP_CHUNK_ANMF;frame.dispose_method=WEBP_MUX_DISPOSE_NONE;frame.blend_method=WEBP_MUX_NO_BLEND;err=WebPMuxPushFrame(s->mux,&frame,1);}
 WebPFree(out);if(err!=WEBP_MUX_OK)return 0;s->added++;return 1;
}
int writer_finish(Writer*s){return s->added==s->count&&WebPMuxAssemble(s->mux,&s->out)==WEBP_MUX_OK;}
const uint8_t*writer_data(Writer*s){return s->out.bytes;}
size_t writer_len(Writer*s){return s->out.size;}
void writer_close(Writer*s){if(s){WebPDataClear(&s->out);WebPMuxDelete(s->mux);free(s);}}

int reader_animated(Reader*r){return r->animated;}
double reader_timebase(Reader*r){(void)r;return 1000.;}
double reader_numerator(Reader*r){return r->duration;}
double reader_denominator(Reader*r){(void)r;return 1000.;}
