/********************************************************************************
*                                                                               *
*                          P C X   I n p u t / O u t p u t                      *
*                                                                               *
*********************************************************************************
* Copyright (C) 2001,2026 by Janusz Ganczarski.   All Rights Reserved.          *
*********************************************************************************
* This library is free software; you can redistribute it and/or modify          *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation; either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* This library 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 Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with this program.  If not, see <http://www.gnu.org/licenses/>          *
********************************************************************************/
#include "xincs.h"
#include "fxver.h"
#include "fxdefs.h"
#include "fxmath.h"
#include "FXArray.h"
#include "FXHash.h"
#include "FXElement.h"
#include "FXStream.h"

/*
  Notes:
  - Loading 1-bit/1-plane, 4-bit/1-plane, 8-bit/1-plane and 8-bit/3-plane
    images appears to work.
  - Need to check if fewer colors, if so fall back on lower pixel depth
    mode to save space.
*/

#define TOPIC_DETAIL 1020

using namespace FX;

/*******************************************************************************/

namespace FX {


#ifndef FXLOADPCX
extern FXAPI FXbool fxcheckPCX(FXStream& store);
extern FXAPI FXbool fxloadPCX(FXStream& store,FXColor*& data,FXint& width,FXint& height);
extern FXAPI FXbool fxsavePCX(FXStream& store,const FXColor *data,FXint width,FXint height);
#endif


// Check if stream contains a PCX
FXbool fxcheckPCX(FXStream& store){
  if(store.direction()==FXStreamLoad){
    FXuchar signature[4];
    store.load(signature,4);
    store.position(-4,FXFromCurrent);
    return signature[0]==10 && (signature[3]==1 || signature[3]==2 || signature[3]==4 || signature[3]==8);
    }
  return false;
  }


// Read possibly compressed scanline
static void readscanline(FXStream& store,FXuchar line[],int size,int compressed){
  FXint count,i=0;
  FXuchar cc;
  if(compressed==1){
    while(i<size){
      count=1;
      store >> cc;
      if(cc>0xc0){
        count=cc-0xc0;
        store >> cc;
        }
      while(count-- && i<size){
        line[i++]=cc;
        }
      }
    }
  else{
    while(i<size){
      store >> cc;
      line[i++]=cc;
      }
    }
  }


// Load PCX image from stream
FXbool fxloadPCX(FXStream& store,FXColor*& data,FXint& width,FXint& height){
  static const FXuchar Mono[2]={0,255};
  FXbool ok=false;

  // Null out
  data=nullptr;
  width=0;
  height=0;

  // Stream must be loading
  if(store.direction()==FXStreamLoad){

    // Check Manufacturer
    FXuchar Manufacturer;
    store >> Manufacturer;
    if(Manufacturer==10){
      FXuchar Version;
      FXuchar Encoding;
      FXuchar BitsPerPixel;

      // Get Version
      store >> Version;

      // Get Encoding
      store >> Encoding;

      // Get BitsPerPixel
      store >> BitsPerPixel;

      // One of these four possibilities?
      if(BitsPerPixel==1 || BitsPerPixel==2 || BitsPerPixel==4 || BitsPerPixel==8){
        FXuchar Colormap[256][3];
        FXuchar NPlanes;
        FXuchar Reserved;
        FXshort PaletteInfo;
        FXshort HRes;
        FXshort VRes;
        FXshort Xmin;
        FXshort Ymin;
        FXshort Xmax;
        FXshort Ymax;
        FXshort BytesPerLine;
        FXuchar fill,*line,*pp,c;
        FXint   NumPixels,TotalBytes,index,shift,clr,x,y,i;
        FXbool  swap;

        // Switch to little-endian
        swap=store.swapBytes();
        store.setBigEndian(false);

        // Get Xmin, Ymin, Xmax, Ymax
        store >> Xmin;
        store >> Ymin;
        store >> Xmax;
        store >> Ymax;

        // HDpi, VDpi
        store >> HRes;
        store >> VRes;

        // Get EGA/VGA Colormap
        store.load(Colormap[0],48);

        // Get Reserved
        store >> Reserved;

        // Get NPlanes
        store >> NPlanes;

        // Does it make sense?
        if(NPlanes==1 || NPlanes==3 || NPlanes==4){

          // Get BytesPerLine
          store >> BytesPerLine;

          // Total bytes for scanline
          TotalBytes=BytesPerLine*NPlanes;

          // Read PaletteInfo
          store >> PaletteInfo;

          // Get 58 bytes, to get to 128 byte header
          for(i=0; i<58; i++) store >> fill;

          // Calculate width and height
          width=Xmax-Xmin+1;
          height=Ymax-Ymin+1;

          // Total number of pixels
          NumPixels=width*height;

          FXTRACE(TOPIC_DETAIL,"fxloadPCX: width=%d height=%d Version=%d BitsPerPixel=%d NPlanes=%d BytesPerLine=%d Encoding=%d\n",width,height,Version,BitsPerPixel,NPlanes,BytesPerLine,Encoding);

          // Scanline buffer
          if(allocElms(line,TotalBytes)){

            // Allocate memory
            if(callocElms(data,NumPixels)){

              // Load 1 bit/pixel
              if(BitsPerPixel==1 && NPlanes==1){
                pp=(FXuchar*)data;
                for(y=0; y<height; y++){
                  readscanline(store,line,BytesPerLine,Encoding);
                  for(x=0; x<width; x++){
                    clr=((FXuchar)(line[x>>3]<<(x&7))>>7);
                    *pp++=Mono[clr];
                    *pp++=Mono[clr];
                    *pp++=Mono[clr];
                    *pp++=255;
                    }
                  }
                }

              // Load 8 bit/pixel
              else if(BitsPerPixel==8 && NPlanes==1){
                pp=(FXuchar*)data;
                for(y=0; y<height; y++){
                  readscanline(store,line,BytesPerLine,Encoding);
                  for(x=0; x<width; x++,pp+=4){
                    *pp=line[x];
                    }
                  }
                store >> c;               // Get VGApaletteID
                if(c==12){;               // Check VGApaletteID
                  store.load(Colormap[0],768);
                  pp=(FXuchar*)data;
                  for(i=0; i<NumPixels; i++){   // Apply colormap
                    clr=pp[0];
                    *pp++=Colormap[clr][2];
                    *pp++=Colormap[clr][1];
                    *pp++=Colormap[clr][0];
                    *pp++=255;
                    }
                  }
                }

              // Load 24 bits/pixel
              else if(BitsPerPixel==8 && NPlanes==3){
                pp=(FXuchar*)data;
                for(y=0; y<height; y++){
                  readscanline(store,line,TotalBytes,Encoding);
                  for(x=0; x<width; x++){
                    *pp++=line[(BytesPerLine<<1)+x];
                    *pp++=line[BytesPerLine+x];
                    *pp++=line[x];
                    *pp++=0xFF;
                    }
                  }
                }

              // Load 4 bit/pixel
              else if((BitsPerPixel==4) || (BitsPerPixel==1 && NPlanes==4)){
                pp=(FXuchar*)data;
                for(y=0; y<height; y++){
                  readscanline(store,line,BytesPerLine*4,Encoding);
                  for(x=0; x<width; x++){
                    clr=0;
                    index=x>>3;
                    shift=7-(x&7);
                    clr|=0x01&((line[index]>>shift));
                    clr|=0x02&((line[index+BytesPerLine]>>shift)<<1);
                    clr|=0x04&((line[index+BytesPerLine*2]>>shift)<<2);
                    clr|=0x08&((line[index+BytesPerLine*3]>>shift)<<3);
                    *pp++=Colormap[clr][2];
                    *pp++=Colormap[clr][1];
                    *pp++=Colormap[clr][0];
                    *pp++=255;
                    }
                  }
                }
              ok=true;
              }

            // Done with that
            freeElms(line);
            }
          }

        // Reset byte order
        store.swapBytes(swap);
        }
      }
    }
  return ok;
  }



/*******************************************************************************/


// Save a PCX file to a stream
FXbool fxsavePCX(FXStream& store,const FXColor *data,FXint width,FXint height){

  // Stream must be saving
  if(store.direction()==FXStreamSave){

    // Must make sense
    if(data && 0<width && 0<height){
      const FXuchar Colormap[16][3]={{0,0,0},{255,255,255},{0,170,0},{0,170,170},{170,0,0},{170,0,170},{170,170,0},{170,170,170},{85,85,85},{85,85,255},{85,255,85},{85,255,255},{255,85,85},{255,85,255},{255,255,85},{255,255,255}};
      const FXuchar *pp;
      FXuchar  Manufacturer=10;
      FXuchar  Version=5;
      FXuchar  Encoding=1;
      FXuchar  BitsPerPixel=8;
      FXuchar  NPlanes=3;
      FXuchar  Reserved=0;
      FXshort  PaletteInfo=1;
      FXshort  HRes=75;
      FXshort  VRes=75;
      FXuchar  fill=0;
      FXshort  Xmin=0;
      FXshort  Ymin=0;
      FXshort  Xmax=width-1;
      FXshort  Ymax=height-1;
      FXshort  BytesPerLine=width;          // FIXME see PCX.txt docs
      FXuchar  Current,Last,RLECount,rc;
      FXint    i,x,y,rgb;
      FXbool   swap;

      // Switch to little-endian
      swap=store.swapBytes();
      store.setBigEndian(false);

      // Manufacturer, Version, Encoding and BitsPerPixel
      store << Manufacturer;
      store << Version;
      store << Encoding;
      store << BitsPerPixel;

      // Xmin = 0
      store << Xmin;

      // Ymin = 0
      store << Ymin;

      // Xmax = width - 1
      store << Xmax;

      // Ymax = height - 1
      store << Ymax;

      // HDpi = 75
      store << HRes;

      // VDpi = 75
      store << VRes;

      // Colormap
      store.save(Colormap[0],48);

      // Reserved
      store << Reserved;

      // NPlanes
      store << NPlanes;

      // BytesPerLine
      store << BytesPerLine;

      // PaletteInfo=1
      store << PaletteInfo;

      // Filler
      for(i=0; i<58; i++) store << fill;

      // Save as 24 bits/pixel
      for(y=0; y<height; y++){
        for(rgb=2; rgb>=0; rgb--){
          pp=((const FXuchar*)(data+y*width))+rgb;
          Last=*pp;
          pp+=4;
          RLECount=1;
          for(x=1; x<width; x++){
            Current=*pp;
            pp+=4;
            if(Current==Last){
              RLECount++;
              if(RLECount==63){
                rc=0xC0|RLECount;
                store << rc << Last;
                RLECount=0;
                }
              }
            else{
              if(RLECount){
                if((RLECount==1) && (0xC0!=(0xC0&Last))){
                  store << Last;
                  }
                else{
                  rc=0xC0|RLECount;
                  store << rc << Last;
                  }
                }
              Last=Current;
              RLECount=1;
              }
            }
          if(RLECount){
            if((RLECount==1) && (0xC0!=(0xC0&Last))){
              store << Last;
              }
            else{
              rc=0xC0|RLECount;
              store << rc << Last;
              }
            }
          }
        }

      // Reset byte order
      store.swapBytes(swap);
      return true;
      }
    }
  return false;
  }

}
