/********************************
FILE: win32.h
Created On: 08/04/2001
Creadted By: David Shultz
********************************/

#ifndef WIN32_H
#define WIN32_H

#include <windows.h>
#include <stdio.h>
#include <stddef.h>
#include <conio.h>
#include "toctool.h"
#include "scsidefs.h"

// Variable inits
BOOL bInit;
INTERFACE interfaces[NUM_INTERFACE];
int iActiveInterface;
int iOSVer;
DWORD (*GetASPI32SupportInfo)(void);
DWORD (*SendASPI32Command)(LPSRB);

int numDrives;
TOC toc;
DRIVELIST driveList;

HINSTANCE hDll;

struct discdata {
	unsigned long discid;
	int num_of_trks;
	int track_offsets[100];
	int seconds;
};

// Function defs
unsigned long CDDBSum(unsigned long);
static int getNumAdapters(void);
static int aspiGetNumDrives(void);
static int ntGetNumDrives(void);
int aspiReadTOC(int, TOC*);
void* genCddbQuery(TOC*);

// Funcs
unsigned long genCddbId(TOC *toc) {
  unsigned long t, n;
  TOCTRACK *t1, *t2;
  int i, numTracks;
  
  if (!toc)
    return 0;
  
  t = n = 0;
  numTracks = (int)(toc->lastTrack - toc->firstTrack + 1);
  for(i = 0; i < numTracks; i++) {
    t1 = &(toc->tracks[i]);
    n += CDDBSum(60 * t1->addr[1] + t1->addr[2]);
  }
  
  t2 = &(toc->tracks[numTracks]);
  t = 60 * t2->addr[1] + t2->addr[2];
  t2 = &(toc->tracks[0]);
  t -= (60 * t2->addr[1] + t2->addr[2]);
  
  return (unsigned long)( ((n % 0xFF) << 24) | 
                             (t << 8) | 
                             ((unsigned long)numTracks));
}

unsigned long CDDBSum(unsigned long n) {
  unsigned long retVal = 0;

  while(n > 0) {
      retVal += (n % 10);
      n /= 10;
    }

  return retVal;
}

void* genCddbQuery(TOC* toc) {
  struct discdata* cd = (struct discdata*)malloc(sizeof(struct discdata));
  int numTracks, i, ofs;
  TOCTRACK *t1;
  
  if (!toc)
    return cd;
  
  numTracks = (int)(toc->lastTrack - toc->firstTrack + 1);

  // cddbid
  cd->discid = genCddbId(toc);
  // number of tracks
  cd->num_of_trks = numTracks;

  // track offsets (not including lead-out)
  for(i = 0; i < numTracks; i++) {
    t1 = &(toc->tracks[i]);
    ofs = (((t1->addr[1] * 60) + t1->addr[2]) * 75) + t1->addr[3];
	cd->track_offsets[i] = ofs;
  }
  
  // disc length
  t1 = &toc->tracks[i];
  ofs = t1->addr[1]*60 + t1->addr[2];
  cd->seconds = ofs;

	return (void*)cd;
}

int getNumDrives(void) {
  if (!bInit)
    return 0;

	return aspiGetNumDrives();
}

void getDriveDesc(int driveNo, char *szBuf, int bufLen) {
  if (!szBuf)
    return;
  
  ZeroMemory(szBuf, bufLen);
  
  if (!bInit)
    return;
  
  if (driveNo > driveList.num)
    return;

	lstrcpyn(szBuf, driveList.drive[driveNo].a.desc, bufLen);
}

static int aspiGetNumDrives( void )
{
  SRB_HAInquiry sh;
  SRB_GDEVBlock sd;
  BYTE numAdapters, maxTgt;
  BYTE i, j, k;
  int idx = 0;
  
  // initialize the drive list;
  ZeroMemory( &driveList, sizeof(driveList) );
  numAdapters = (BYTE)getNumAdapters();
  if ( numAdapters == 0 )
    return 0;
  
  for( i = 0; i < numAdapters; i++ )
  {
    ZeroMemory( &sh, sizeof(sh) );
    sh.SRB_Cmd = SC_HA_INQUIRY;
    sh.SRB_HaID = i;
    SendASPI32Command( (LPSRB)&sh );

    // in case of error, skip to next adapter
    if ( sh.SRB_Status != SS_COMP )
      continue;
    
    // determine the max target number for the adapter from offset 3
    // if it's zero, then assume the max is 8
    maxTgt = sh.HA_Unique[3];
    if ( maxTgt == 0 )
      maxTgt = 8;

    for( j = 0; j < maxTgt; j++ )
    {
      // try all 8 values for LUN
      for( k = 0; k < 8; k++ )
      {
        ZeroMemory( &sd, sizeof(sd) );
        sd.SRB_Cmd   = SC_GET_DEV_TYPE;
        sd.SRB_HaID  = i;
        sd.SRB_Target = j;
        sd.SRB_Lun   = k;
        SendASPI32Command( (LPSRB)&sd );
        if ( sd.SRB_Status == SS_COMP )
        {
          if ( sd.SRB_DeviceType == DTYPE_CDROM && driveList.num <= MAX_DRIVE_LIST )
          {
            idx = driveList.num++;
            driveList.drive[idx].a.ha = i;
            driveList.drive[idx].a.tgt = j;
            driveList.drive[idx].a.lun = k;
            wsprintf( driveList.drive[idx].a.desc, "ASPI[%d:%d:%d]", i, j, k );
          }
        }
      }
    }
  }
  
  return driveList.num;
}

/************************************************************************
 * ASPI layer
 ************************************************************************/
static int getNumAdapters( void )
{
  DWORD d;
  BYTE bCount, bStatus;

  d = GetASPI32SupportInfo();
  bCount = LOBYTE(LOWORD(d));
  bStatus = HIBYTE(LOWORD(d));
  
  if ( bStatus != SS_COMP && bStatus != SS_NO_ADAPTERS )
    return -1;
  
  return (int)bCount;
}

int readTOC(int driveNo, TOC* t) {
	if (!bInit || (driveNo > driveList.num))
		return -1;

	return aspiReadTOC(driveNo, t);
}

int aspiReadTOC(int driveNo, TOC* t) {
	HANDLE hEvent;
	SRB_ExecSCSICmd s;
	DWORD dwStatus;

	if (driveNo > driveList.num)
		return -2;

	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	ZeroMemory(&s, sizeof(s));

	s.SRB_Cmd = SC_EXEC_SCSI_CMD;
	s.SRB_HaID = driveList.drive[driveNo].a.ha;
	s.SRB_Target = driveList.drive[driveNo].a.tgt;
	s.SRB_Lun = driveList.drive[driveNo].a.lun;
	s.SRB_Flags = SRB_DIR_IN | SRB_EVENT_NOTIFY;
	s.SRB_BufLen = 0x324;
	s.SRB_BufPointer = (BYTE FAR*)t;
	s.SRB_SenseLen = 0x0E;
	s.SRB_CDBLen = 0x0A;
	s.SRB_PostProc = (LPVOID)hEvent;
	s.CDBByte[0] = 0x43;
	s.CDBByte[1] = 0x02;
	s.CDBByte[7] = 0x03;
	s.CDBByte[8] = 0x24;

	ResetEvent(hEvent);

	dwStatus = SendASPI32Command((LPSRB)&s);

	if (dwStatus == SS_PENDING)
		WaitForSingleObject(hEvent, 10000); // wait up to 10 secs.

	CloseHandle(hEvent);

	if (s.SRB_Status != SS_COMP)
		return -3;

	return 0;
}

/*
 * Initialize functions according to whether we're using ASPI (Win95/98)
 * or the CDROM ioctls (NT/2000);
 */
int initTool(void) {
  
  if (bInit)
    return 0;
  
  ZeroMemory(interfaces, sizeof(interfaces));
  lstrcpy(interfaces[INTERFACE_ASPI].name, "ASPI");
  
  iOSVer = getOsVersion();
  if (iOSVer == OS_UNKNOWN)
    return -1;

  // check if aspi is available
  hDll = LoadLibrary("WNASPI32.DLL");
  GetASPI32SupportInfo = 
    (DWORD(*)(void))GetProcAddress(hDll, "GetASPI32SupportInfo");
  SendASPI32Command = 
    (DWORD(*)(LPSRB))GetProcAddress(hDll, "SendASPI32Command");

  // make sure that we've got both function addresses
  if (GetASPI32SupportInfo && SendASPI32Command) {
    interfaces[INTERFACE_ASPI].avail = TRUE;
  }
  
  bInit = TRUE;
  return 0;
}

/*
 * Returns the current OS system, one of OS_WIN95, OS_WIN98,
 * OS_WINNT35, OS_WINNT4, OS_WIN2K, or if an error occurs,
 * OS_UNKNOWN
 */
int getOsVersion(void) {
  OSVERSIONINFO os;
  
  ZeroMemory(&os, sizeof(os));
  os.dwOSVersionInfoSize = sizeof(os);
  GetVersionEx(&os);

  if (os.dwPlatformId == VER_PLATFORM_WIN32_NT) {
    if (os.dwMajorVersion == 3 && os.dwMinorVersion >= 50)
      return OS_WINNT35;
    else if (os.dwMajorVersion == 4)
      return OS_WINNT4;
    else if (os.dwMajorVersion == 5)
      return OS_WIN2K;
  }
  else if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
    if (os.dwMinorVersion == 0)
      return OS_WIN95;
    else
      return OS_WIN98;
  }
  
  return OS_UNKNOWN;  
}

unsigned long discid(int drive) {
	struct discdata* cd;
	int i, numTracks;
	unsigned long id;

	i = initTool();
	getNumDrives();
	ZeroMemory(&toc, sizeof(toc));
	readTOC(drive, &toc);

	if (!toc.firstTrack && !toc.lastTrack)
		numTracks = 0;
	else
		numTracks = (int)(toc.lastTrack - toc.firstTrack + 1);
  
	if (numTracks > 0) {
		// calculate the cddbId
		cd = genCddbQuery(&toc);
		id = cd->discid;
		free(cd);
	}
	return id;
}

struct discdata get_disc_id(int drive) {
	struct discdata* cd;
	struct discdata data;
	int i, numTracks;
	unsigned long id;

	i = initTool();
	getNumDrives();
	ZeroMemory(&toc, sizeof(toc));
	readTOC(drive, &toc);
	if (!toc.firstTrack && !toc.lastTrack) {
		static char foo[2048];
		numTracks = 0;
	}
	else
		numTracks = (int)(toc.lastTrack - toc.firstTrack + 1);
  
	if (numTracks > 0) {
		// calculate the cddbId
		cd = genCddbQuery(&toc);
	}

	free(cd);
	data = *cd;

	return data;
}

#endif //WIN32_H