//
//  Lynkeos
//  $Id: $
//
//  Created by Jean-Etienne LAMIAUD on Fri Sep 28 2018.
//  Copyright (c) 2018-2023. Jean-Etienne LAMIAUD
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// 

#include <LynkeosCore/LynkeosProcessing.h>
#include <LynkeosCore/LynkeosMetadata.h>
#include <LynkeosCore/LynkeosInterpolator.h>

#include "SER_ReaderPrefs.h"
#include "SER_Reader.h"

/*!
 * @abstract Internals of the SER reader
 * @discussion
 * @ingroup FileAccess
 */
@interface SER_Reader(Private)
@end

@implementation SER_Reader(Private)
@end

@implementation SER_Reader

+ (void) load  // Only to force the runtime to load the class
{
}

+ (void) lynkeosFileTypes:(NSArray**)fileTypes
{
   // This reader has priority over non specialized SER readers such as FFMpeg
   *fileTypes = [NSArray arrayWithObjects: [NSNumber numberWithInt:1], @"ser",nil];
}

- (id) init
{
   self = [super init];
   if ( self != nil )
   {
      NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
      _file = NULL;
      _numberOfFrames = 0;
      _numberOfPlanes = 0;
      _width = 0;
      _height = 0;
      _bytesPerPlane = 0;
      _bytesPerPixel = 0;
      _bytesPerRow = 0;
      _filePos = 0;
      _format = SER_MONO;
      _byteOrder = SER_LITTLE_ENDIAN;
      double rw = [prefs doubleForKey:K_SER_RED_KEY];
      _whiteBalance[RED_PLANE] = (rw > 0.0 ? rw : 1.0);
      double rg = [prefs doubleForKey:K_SER_GREEN_KEY];
      _whiteBalance[GREEN_PLANE] = (rg > 0.0 ? rg : 1.0);
      double rb = [prefs doubleForKey:K_SER_BLUE_KEY];
      _whiteBalance[BLUE_PLANE] = (rb > 0.0 ? rb : 1.0);
      _metadata = [[NSMutableDictionary alloc] init];
      _mutex = [[NSLock alloc] init];
   }
   return( self );
}

- (id) initWithURL:(NSURL*)url
{
   int          ret;
   SER_Header_t ser;

   self = [self init];

   if ( self != nil )
   {
      // Open video file
      _file = fopen([[url path] fileSystemRepresentation], "rb");
      if ( _file == NULL )
      {
         NSLog( @"Could not open file %@", [url absoluteString] );
         [self release];
         return( nil );
      }

      // Retrieve stream information
      ret = SER_read_header(_file, &ser);
      if ( ret < 0 )
      {
         NSLog( @"Could not read SER header");
         [self release];
         return( nil );
      }

      _numberOfFrames = ser.FrameCount;
      _width = ser.ImageWidth;
      _height = ser.ImageHeight;
      _bytesPerPlane = (ser.PixelDepthPerPlane + 7)/8;
      _format = ser.ColorID;
      _byteOrder = ser.LittleEndian;
      switch (_format)
      {
         case SER_MONO:
            _numberOfPlanes = 1;
            _bytesPerPixel = _bytesPerPlane;
            break;

         case SER_BAYER_RGGB:
         case SER_BAYER_GRBG:
         case SER_BAYER_GBRG:
         case SER_BAYER_BGGR:
         case SER_BAYER_CYYM:
         case SER_BAYER_YCMY:
         case SER_BAYER_YMCY:
         case SER_BAYER_MYYC:
            // Unsupported by this variant, will be opened by the RAW Bayer variant
            [self release];
            return( nil );
            break;

         case SER_RGB:
         case SER_BGR:
            _numberOfPlanes = 3;
            _bytesPerPixel = _numberOfPlanes * _bytesPerPlane;
            break;

         default:
            NSLog(@"Unknown pixel format %d", _format);
            [self release];
            return( nil );
            break;
      }
      _bytesPerRow = _width * _bytesPerPixel;

      // Retrieve metadata
      if (strnlen(ser.Observer, SER_STRING_LENGTH) != 0)
         [_metadata setObject:[NSArray arrayWithObject:[NSString stringWithCString:ser.Observer
                                                                          encoding:NSUTF8StringEncoding]]
                       forKey:LynkeosMD_Authors()];
      if (strnlen(ser.Instrument, SER_STRING_LENGTH) != 0)
         [_metadata setObject:[NSString stringWithCString:ser.Instrument encoding:NSUTF8StringEncoding]
                       forKey:LynkeosMD_CameraModel()];
      if (strnlen(ser.Telescope, SER_STRING_LENGTH) != 0)
         [_metadata setObject:[NSString stringWithCString:ser.Telescope encoding:NSUTF8StringEncoding]
                       forKey:LynkeosMD_Telescope()];
      if (ser.DateTime_UTC >= 0)
         [_metadata setObject:[NSDate dateWithTimeIntervalSinceReferenceDate:
                                  (NSTimeInterval)(ser.DateTime_UTC - SER_DATE_ORIGIN)*SER_DATE_TIMEBASE]
                       forKey:LynkeosMD_CaptureDate()];
   }

   return( self );
}

- (void) dealloc
{
   if (_file != NULL)
      fclose(_file);
   [_metadata release];
   [_mutex release];

   [super dealloc];
}

- (void) imageWidth:(u_short*)w height:(u_short*)h
{   
   *w = _width;
   *h = _height;
}

- (u_short) numberOfPlanes
{
   return( _numberOfPlanes );
}

- (void) getMinLevel:(double*)vmin maxLevel:(double*)vmax
{
   *vmin = 0.0;
   *vmax = 255.0;
}

- (u_long) numberOfFrames
{
   return( _numberOfFrames );
}

- (NSImage*) getNSImageAtIndex:(u_long)index
{
   NSImage *image = nil;
   NSBitmapImageRep* bitmap;

   NSAssert( index < _numberOfFrames, @"Access beyond sequence end" );

   // Create a RGB bitmap
   bitmap = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                                                   pixelsWide:_width
                                                   pixelsHigh:_height
                                                  bitsPerSample:8
                                                samplesPerPixel:_numberOfPlanes
                                                       hasAlpha:NO
                                                       isPlanar:NO
                                                 colorSpaceName: _numberOfPlanes == 1 ?
                                                                    NSCalibratedWhiteColorSpace :
                                                                    NSCalibratedRGBColorSpace
                                                    bytesPerRow:0
                                                   bitsPerPixel:0]
             autorelease];

   if ( bitmap != nil )
   {
      u_char *pixels = (u_char*)[bitmap bitmapData];
      int bpr = (int)[bitmap bytesPerRow];
      int bpp = (int)[bitmap bitsPerPixel]/8;
      const size_t imageSize = _height*_bytesPerRow;
      void *buffer = malloc(imageSize);
      const off_t imageOffset = SER_START_OF_IMAGES + index*imageSize;
      u_short x, y, p;
      int ret = 0;
      size_t nread = 0;

      [_mutex lock];

      if (_filePos != imageOffset)
         ret = fseeko(_file, imageOffset, SEEK_SET);
      if (ret == 0)
         nread = fread(buffer, _bytesPerRow, _height, _file);
      if (nread == _height)
         _filePos = imageOffset + imageSize;

      [_mutex unlock];

      if (nread == _height)
      {
         for( y = 0; y < _height; y++ )
         {
            void *linePtr = buffer + y*_bytesPerRow;
            for( x = 0; x < _width; x++ )
            {
               void *pixPtr = linePtr + x*_bytesPerPixel;
               u_char v;
               for ( p = 0 ; p < _numberOfPlanes; p++ )
               {
                  if (_bytesPerPlane == 2)
                  {
                     uint16 iv;
                     if (_format == SER_BGR)
                        iv = ((uint16*)pixPtr)[2-p];
                     else
                        iv = ((uint16*)pixPtr)[p];
                     if (_byteOrder == SER_BIG_ENDIAN)
                        v = CFSwapInt16BigToHost(iv)/256;
                     else
                        v = CFSwapInt16LittleToHost(iv)/256;
                  }
                  else
                  {
                     if (_format == SER_BGR)
                        v = ((uint8*)pixPtr)[2-p];
                     else
                        v = ((uint8*)pixPtr)[p];
                  }
                  pixels[y*bpr+x*bpp+p] = v;
               }
            }
         }
      }

      free(buffer);

      image = [[[NSImage alloc] initWithSize:NSMakeSize(_width, _height)] autorelease];

      if ( image != nil )
         [image addRepresentation:bitmap];
   }

   return( image );
}

- (void) getImageSample:(REAL * const * const)sample atIndex:(u_long)index
             withPlanes:(u_short)nPlanes
                    atX:(short)x Y:(short)y W:(u_short)w H:(u_short)h
              lineWidth:(u_short)lineW
{
   NSAssert(x >= 0 && (x+w) <= _width && y >= 0 && (y+h) <= _height, @"SER reader: Attempt to read outside of image");
   // Read the data
   const size_t imageSize = _height*_bytesPerRow;
   void *buffer = malloc(imageSize);
   const off_t imageOffset = SER_START_OF_IMAGES + index*imageSize;
   int ret = 0;
   size_t nread = 0;
   u_short xl, yl, p;
   REAL v;

   [_mutex lock];

   if (_filePos != imageOffset)
      ret = fseeko(_file, imageOffset, SEEK_SET);
   if (ret == 0)
      nread = fread(buffer, _bytesPerRow, _height, _file);
   if (nread == _height)
      _filePos = imageOffset + imageSize;

   [_mutex unlock];

   if (nread == _height)
   {
      for( yl = 0; yl < h; yl++ )
      {
         void *linePtr = buffer + (y + yl)*_bytesPerRow;
         for( xl = 0; xl < w; xl++ )
         {
            void *pixPtr = linePtr + (x + xl)*_bytesPerPixel;

            for ( p = 0 ; p < nPlanes; p++ )
            {
               if (_bytesPerPlane == 2)
               {
                  uint16 iv;
                  if (_format == SER_BGR)
                     iv = ((uint16*)pixPtr)[2-p];
                  else
                     iv = ((uint16*)pixPtr)[p];
                  if (_byteOrder == SER_BIG_ENDIAN)
                     v = (REAL)CFSwapInt16BigToHost(iv)/256.0;
                  else
                     v = (REAL)CFSwapInt16LittleToHost(iv)/256.0;
                  v *= _whiteBalance[p];
                  if (v >= 256.0)
                     v = 65535.0/256.0;
               }
               else
               {
                  if (_format == SER_BGR)
                     v = ((uint8*)pixPtr)[2-p];
                  else
                     v = ((uint8*)pixPtr)[p];
                  v *= _whiteBalance[p];
                  if (v > 255.0)
                     v = 255.0;
               }
               SET_SAMPLE(sample[p], xl, yl, lineW, v);
            }
         }
      }
   }

   free(buffer);
}

- (NSDictionary*) getMetaData 
{
   return( _metadata );
}

@end
