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

// TODO: rewrite
// FIXME: cleanup 'static' variables

typedef unsigned char byte;
/* byte must be 8 bits */

/* int must be at least 16 bits */

/* long must be at least 32 bits */



#define DIE_MSG( x ) \
        { MSG( x ); exit( 1 ); }


#define UPPER_NIBBLE( x ) \
        (((128|64|32|16) & (x)) >> 4)

#define LOWER_NIBBLE( x ) \
        ((1|2|4|8) & (x))

#define COMBINE_HI_8LO( hi, lo ) \
        ( (((unsigned)hi) << 8) | (unsigned)lo )

#define COMBINE_HI_4LO( hi, lo ) \
        ( (((unsigned)hi) << 4) | (unsigned)lo )

const byte edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff,
                                0xff, 0xff, 0xff, 0x00 };

const byte edid_v1_descriptor_flag[] = { 0x00, 0x00 };


#define EDID_LENGTH                             0x80

#define EDID_HEADER                             0x00
#define EDID_HEADER_END                         0x07

#define ID_MANUFACTURER_NAME                    0x08
#define ID_MANUFACTURER_NAME_END                0x09
#define ID_MODEL				0x0a

#define ID_SERIAL_NUMBER			0x0c

#define MANUFACTURE_WEEK			0x10
#define MANUFACTURE_YEAR			0x11

#define EDID_STRUCT_VERSION                     0x12
#define EDID_STRUCT_REVISION                    0x13

#define DPMS_FLAGS				0x18
#define ESTABLISHED_TIMING_1                    0x23
#define ESTABLISHED_TIMING_2                    0x24
#define MANUFACTURERS_TIMINGS                   0x25

#define DETAILED_TIMING_DESCRIPTIONS_START      0x36
#define DETAILED_TIMING_DESCRIPTION_SIZE        18
#define NO_DETAILED_TIMING_DESCRIPTIONS         4



#define DETAILED_TIMING_DESCRIPTION_1           0x36
#define DETAILED_TIMING_DESCRIPTION_2           0x48
#define DETAILED_TIMING_DESCRIPTION_3           0x5a
#define DETAILED_TIMING_DESCRIPTION_4           0x6c



#define PIXEL_CLOCK_LO     (unsigned)dtd[ 0 ]
#define PIXEL_CLOCK_HI     (unsigned)dtd[ 1 ]
#define PIXEL_CLOCK        (COMBINE_HI_8LO( PIXEL_CLOCK_HI,PIXEL_CLOCK_LO )*10000)

#define H_ACTIVE_LO        (unsigned)dtd[ 2 ]

#define H_BLANKING_LO      (unsigned)dtd[ 3 ]

#define H_ACTIVE_HI        UPPER_NIBBLE( (unsigned)dtd[ 4 ] )

#define H_ACTIVE           COMBINE_HI_8LO( H_ACTIVE_HI, H_ACTIVE_LO )

#define H_BLANKING_HI      LOWER_NIBBLE( (unsigned)dtd[ 4 ] )

#define H_BLANKING         COMBINE_HI_8LO( H_BLANKING_HI, H_BLANKING_LO )




#define V_ACTIVE_LO        (unsigned)dtd[ 5 ]

#define V_BLANKING_LO      (unsigned)dtd[ 6 ]

#define V_ACTIVE_HI        UPPER_NIBBLE( (unsigned)dtd[ 7 ] )

#define V_ACTIVE           COMBINE_HI_8LO( V_ACTIVE_HI, V_ACTIVE_LO )

#define V_BLANKING_HI      LOWER_NIBBLE( (unsigned)dtd[ 7 ] )

#define V_BLANKING         COMBINE_HI_8LO( V_BLANKING_HI, V_BLANKING_LO )



#define H_SYNC_OFFSET_LO   (unsigned)dtd[ 8 ]
#define H_SYNC_WIDTH_LO    (unsigned)dtd[ 9 ]

#define V_SYNC_OFFSET_LO   UPPER_NIBBLE( (unsigned)dtd[ 10 ] )
#define V_SYNC_WIDTH_LO    LOWER_NIBBLE( (unsigned)dtd[ 10 ] )

#define V_SYNC_WIDTH_HI    ((unsigned)dtd[ 11 ] & (1|2))
#define V_SYNC_OFFSET_HI   (((unsigned)dtd[ 11 ] & (4|8)) >> 2)

#define H_SYNC_WIDTH_HI    (((unsigned)dtd[ 11 ] & (16|32)) >> 4)
#define H_SYNC_OFFSET_HI   (((unsigned)dtd[ 11 ] & (64|128)) >> 6)


#define V_SYNC_WIDTH       COMBINE_HI_4LO( V_SYNC_WIDTH_HI, V_SYNC_WIDTH_LO )
#define V_SYNC_OFFSET      COMBINE_HI_4LO( V_SYNC_OFFSET_HI, V_SYNC_OFFSET_LO )

#define H_SYNC_WIDTH       COMBINE_HI_4LO( H_SYNC_WIDTH_HI, H_SYNC_WIDTH_LO )
#define H_SYNC_OFFSET      COMBINE_HI_4LO( H_SYNC_OFFSET_HI, H_SYNC_OFFSET_LO )

#define H_SIZE_LO          (unsigned)dtd[ 12 ]
#define V_SIZE_LO          (unsigned)dtd[ 13 ]

#define H_SIZE_HI          UPPER_NIBBLE( (unsigned)dtd[ 14 ] )
#define V_SIZE_HI          LOWER_NIBBLE( (unsigned)dtd[ 14 ] )

#define H_SIZE             COMBINE_HI_8LO( H_SIZE_HI, H_SIZE_LO )
#define V_SIZE             COMBINE_HI_8LO( V_SIZE_HI, V_SIZE_LO )

#define H_BORDER           (unsigned)dtd[ 15 ]
#define V_BORDER           (unsigned)dtd[ 16 ]

#define FLAGS              (unsigned)dtd[ 17 ]

#define INTERLACED         (FLAGS&128)
#define SYNC_TYPE	   (FLAGS&3<<3)  /* bits 4,3 */
#define SYNC_SEPARATE	   (3<<3)
#define HSYNC_POSITIVE	   (FLAGS & 4)
#define VSYNC_POSITIVE     (FLAGS & 2)

#define MONITOR_NAME            0xfc
#define MONITOR_LIMITS          0xfd
#define UNKNOWN_DESCRIPTOR      -1
#define DETAILED_TIMING_BLOCK   -2


#define DESCRIPTOR_DATA         5
#define V_MIN_RATE              block[ 5 ]
#define V_MAX_RATE              block[ 6 ]
#define H_MIN_RATE              block[ 7 ]
#define H_MAX_RATE              block[ 8 ]

#define MAX_PIXEL_CLOCK         (((int)block[ 9 ]) * 10)
#define GTF_SUPPORT		block[10]

#define DPMS_ACTIVE_OFF		(1 << 5)
#define DPMS_SUSPEND		(1 << 6)
#define DPMS_STANDBY		(1 << 7)

char* myname;

void MSG( const char* x )
{
  fprintf( stderr, "%s: %s\n", myname, x ); 
}


int
parse_edid( byte* edid );


int
parse_timing_description( byte* dtd );


int
parse_monitor_limits( byte* block );

int
block_type( byte* block );

char*
get_monitor_name( byte const*  block );

char*
get_vendor_sign( byte const* block );

int
parse_dpms_capabilities( byte flags );

int
main( int argc, char** argv )
{
  byte edid[ EDID_LENGTH ];
  FILE* edid_file;

  myname = argv[ 0 ];
  fprintf( stderr, "%s: parse-edid version %s\n", myname, VERSION );
  
  if ( argc > 2 )
    {
      DIE_MSG( "syntax: [input EDID file]" );
    }
  else
    {
      if ( argc == 2 )
	{
	  edid_file = fopen( argv[ 1 ], "rb" );
	  if ( !edid_file )
	    DIE_MSG( "unable to open file for input" );
	}
      
      else
	edid_file = stdin;
    }

  if ( fread( edid, sizeof( byte ), EDID_LENGTH, edid_file )
       != EDID_LENGTH )

    {
      DIE_MSG( "IO error reading EDID" );
    }

  fclose( edid_file );

  return parse_edid( edid );
}

int
parse_edid( byte* edid )
{
  unsigned i;
  byte* block;
  char* monitor_name = NULL;
  char monitor_alt_name[100];
  byte checksum = 0;
  char *vendor_sign;
  int ret = 0;
  
  for( i = 0; i < EDID_LENGTH; i++ )
    checksum += edid[ i ];

  if (  checksum != 0  ) {
      MSG( "EDID checksum failed - data is corrupt. Continuing anyway." );
      ret = 1;
  } else 
      MSG( "EDID checksum passed." );
  

  if ( strncmp( edid+EDID_HEADER, edid_v1_header, EDID_HEADER_END+1 ) )
    {
      MSG( "first bytes don't match EDID version 1 header" );
      MSG( "do not trust output (if any)." );
      ret = 1;
    }

  printf( "\n\t# EDID version %d revision %d\n", (int)edid[EDID_STRUCT_VERSION],(int)edid[EDID_STRUCT_REVISION] );

  vendor_sign = get_vendor_sign( edid + ID_MANUFACTURER_NAME ); 

  printf( "Section \"Monitor\"\n" );

  block = edid + DETAILED_TIMING_DESCRIPTIONS_START;

  for( i = 0; i < NO_DETAILED_TIMING_DESCRIPTIONS; i++,
	 block += DETAILED_TIMING_DESCRIPTION_SIZE )
    {

      if ( block_type( block ) == MONITOR_NAME )
	{
	  monitor_name = get_monitor_name( block );
	  break;
	}
    }

  if (!monitor_name) {
    /* Stupid djgpp hasn't snprintf so we have to hack something together */
    if(strlen(vendor_sign) + 10 > sizeof(monitor_alt_name))
      vendor_sign[3] = 0;
    
    sprintf(monitor_alt_name, "%s:%02x%02x",
	    vendor_sign, edid[ID_MODEL], edid[ID_MODEL+1]) ;
    monitor_name = monitor_alt_name;
  }

  printf( "\tIdentifier \"%s\"\n", monitor_name );
  printf( "\tVendorName \"%s\"\n", vendor_sign );
  printf( "\tModelName \"%s\"\n", monitor_name );

  block = edid + DETAILED_TIMING_DESCRIPTIONS_START;

  for( i = 0; i < NO_DETAILED_TIMING_DESCRIPTIONS; i++,
	 block += DETAILED_TIMING_DESCRIPTION_SIZE )
    {

      if ( block_type( block ) == MONITOR_LIMITS )
	parse_monitor_limits( block );
    }

  parse_dpms_capabilities(edid[DPMS_FLAGS]);

  block = edid + DETAILED_TIMING_DESCRIPTIONS_START;

  for( i = 0; i < NO_DETAILED_TIMING_DESCRIPTIONS; i++,
	 block += DETAILED_TIMING_DESCRIPTION_SIZE )
    {

      if ( block_type( block ) == DETAILED_TIMING_BLOCK )
	parse_timing_description( block );
    }


  printf( "EndSection\n" );

  return ret;
}


int
parse_timing_description( byte* dtd )
{
  int htotal, vtotal;
  htotal = H_ACTIVE + H_BLANKING;
  vtotal = V_ACTIVE + V_BLANKING;
  
  printf( "\tMode \t\"%dx%d\"", H_ACTIVE, V_ACTIVE );
  printf( "\t# vfreq %3.3fHz, hfreq %6.3fkHz\n",
	  (double)PIXEL_CLOCK/((double)vtotal*(double)htotal),
	  (double)PIXEL_CLOCK/(double)(htotal*1000));

  printf( "\t\tDotClock\t%f\n", (double)PIXEL_CLOCK/1000000.0 );

  printf( "\t\tHTimings\t%u %u %u %u\n", H_ACTIVE,
	  H_ACTIVE+H_SYNC_OFFSET, 
	  H_ACTIVE+H_SYNC_OFFSET+H_SYNC_WIDTH,
	  htotal );

  printf( "\t\tVTimings\t%u %u %u %u\n", V_ACTIVE,
	  V_ACTIVE+V_SYNC_OFFSET,
	  V_ACTIVE+V_SYNC_OFFSET+V_SYNC_WIDTH,
	  vtotal );

  if ( INTERLACED || (SYNC_TYPE == SYNC_SEPARATE)) {
    printf( "\t\tFlags\t%s\"%sHSync\" \"%sVSync\"\n",
	INTERLACED ? "\"Interlace\" ": "",
	HSYNC_POSITIVE ? "+": "-",
	VSYNC_POSITIVE ? "+": "-");
  }

  printf( "\tEndMode\n" );

  return 0;
}


int
block_type( byte* block )
{
  if ( !strncmp( edid_v1_descriptor_flag, block, 2 ) )
    {
      printf("\t# Block type: 2:%x 3:%x\n", block[2], block[3]);

      /* descriptor */

      if ( block[ 2 ] != 0 )
	return UNKNOWN_DESCRIPTOR;


      return block[ 3 ];
    } else {

      /* detailed timing block */

      return DETAILED_TIMING_BLOCK;
    }
}

char*
get_monitor_name( byte const* block )
{
  static char name[ 13 ];
  unsigned i;
  byte const* ptr = block + DESCRIPTOR_DATA;


  for( i = 0; i < 13; i++, ptr++ )
    {

      if ( *ptr == 0xa )
	{
	  name[ i ] = 0;
	  return name;
	}

      name[ i ] = *ptr;
    }


  return name;
}


char* get_vendor_sign( byte const* block )
{
  static char sign[4];
  unsigned short h;

  /*
     08h	WORD	big-endian manufacturer ID (see #00136)
		    bits 14-10: first letter (01h='A', 02h='B', etc.)
		    bits 9-5: second letter
		    bits 4-0: third letter
  */
  h = COMBINE_HI_8LO(block[0], block[1]);
  sign[0] = ((h>>10) & 0x1f) + 'A' - 1;
  sign[1] = ((h>>5) & 0x1f) + 'A' - 1;
  sign[2] = (h & 0x1f) + 'A' - 1;
  sign[3] = 0;
  return sign;
}

int
parse_monitor_limits( byte* block )
{
  printf( "\tHorizSync %u-%u\n", H_MIN_RATE, H_MAX_RATE );
  printf( "\tVertRefresh %u-%u\n", V_MIN_RATE, V_MAX_RATE );
  if ( MAX_PIXEL_CLOCK == 10*0xff )
    printf( "\t# Max dot clock not given\n" );
  else
    printf( "\t# Max dot clock (video bandwidth) %u MHz\n", (int)MAX_PIXEL_CLOCK );

  if ( GTF_SUPPORT )
    {
      printf( "\t# EDID version 3 GTF given: contact author\n" );
    }
  
  return 0;
}

int
parse_dpms_capabilities(byte flags)
{
  printf("\t# DPMS capabilities: Active off:%s  Suspend:%s  Standby:%s\n\n",
    (flags & DPMS_ACTIVE_OFF) ? "yes" : "no",
    (flags & DPMS_SUSPEND)    ? "yes" : "no",
    (flags & DPMS_STANDBY)    ? "yes" : "no");
  return 0;
}
    
