/**
 *
 *  file descriptor
 *
 *  各システム間のファイルシステムの違いは
 *  なんとかここで吸収する
 *
 */


#ifndef FILEDESC_H
#define FILEDESC_H


#ifndef STRICT
#  define STRICT
#endif
#include <windows.h>

// 定義されていない！？
#ifndef INVALID_SET_FILE_POINTER
#  define INVALID_SET_FILE_POINTER 0xffffffff
#endif

typedef HANDLE FileHandle;


///////////////////////////////////////////////////////////////////////////////

#define INVALID_ARGUMENT_EXCEPTION() do { } while(0)

#define API_FAILED_EXCEPTION( _err ) throw "api failed"

#define ASSERTION_FAILED_EXCEPTION() throw "assertin failed"



///////////////////////////////////////////////////////////////////////////////
/*!
 * 基本的なファイル記述子のラップ
 */
class FileDescriptor {
  bool eof_;
  FileHandle file_handle_;


public:
  //! 読み / 書き
  enum Mode {
    mdREAD,  // 読み
    mdWRITE, // 書き
    mdBOTH   // 両方
  };
#define FD_mdREAD  FileDescriptor::mdREAD
#define FD_mdWRITE FileDescriptor::mdWRITE
#define FD_mdBOTH  FileDescriptor::mdBOTH

  //! ファイルが存在する場合
  enum IfExists {
    exERROR,  // エラーとする
    exOPEN,   // 開く
    exCLEAR   // 内容は破棄する
  };
#define FD_exERROR FileDescriptor::exERROR
#define FD_exOPEN  FileDescriptor::exOPEN
#define FD_exCLEAR FileDescriptor::exCLEAR

  //! ファイルが存在しない場合
  enum IfNotExists {
    neERROR,  // エラーとする
    neCREATE  // 生成する
  };
#define FD_neERROR  FileDescriptor::neERROR
#define FD_neCREATE FileDescriptor::neCREATE

  //! position origin
  enum Origin {
    FROM_BEGIN,
    FROM_HERE,
    FROM_END
  };
#define FD_FROM_BEGIN FileDescriptor::FROM_BEGIN
#define FD_FROM_HERE  FileDescriptor::FROM_HERE
#define FD_FROM_END   FileDescriptor::FROM_END

  //===========================================================================
  /*! コンストラクタ
   * ex に exERROR を指定して ne に neERROR を指定するか
   * ne に neERROR を指定して ex に exERROR を指定すると InvalidArgumentException
   *
   * @param fname ファイル名
   * @param md   mdREAD=読み  mdWRITE=書き  mdBOTH=両方
   * @param ex   ファイルが存在する場合 exERROR=エラー  exOPEN=そのまま開く  exCLEAR=クリアする
   * @param ne   ファイルがない場合   neERROR=エラー  neCREATE=生成する
   *
   * @exception  XInvalidArgument   不正な引数
   * @exception  XAPIFailed          API が失敗 - エラー番号 getError を参照のこと
   */
  FileDescriptor(const char *fname, Mode md, IfExists ex, IfNotExists ne) {
    eof_ = true;
    file_handle_ = INVALID_HANDLE_VALUE;

    DWORD desired_access;
    DWORD share_mode = 0;
    DWORD creation_distribution;
    DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL;

    if (!fname) {
      INVALID_ARGUMENT_EXCEPTION();
      return;
    }
    if (ex == exERROR && ne == neERROR) {
      INVALID_ARGUMENT_EXCEPTION();
      return;
    }

    // 読み/書き
    switch(md) {
    case mdREAD : desired_access = GENERIC_READ;  break;
    case mdWRITE: desired_access = GENERIC_WRITE; break;
    case mdBOTH : desired_access = GENERIC_READ | GENERIC_WRITE; break;
    default: INVALID_ARGUMENT_EXCEPTION();  return;
    }

    // ファイルを開く時の挙動
    switch(ne) {
    case neERROR:
      {
        switch(ex) {
        case exERROR: INVALID_ARGUMENT_EXCEPTION();  return;
        case exOPEN : creation_distribution = OPEN_EXISTING; break;
        case exCLEAR: creation_distribution = TRUNCATE_EXISTING; break;
        default: INVALID_ARGUMENT_EXCEPTION();  return;
        }
      }
      break;

    case neCREATE:
      {
        switch(ex) {
        case exERROR: creation_distribution = CREATE_NEW; break;
        case exOPEN : creation_distribution = OPEN_ALWAYS; break;
        case exCLEAR: creation_distribution = CREATE_ALWAYS; break;
        default: INVALID_ARGUMENT_EXCEPTION();  return;
        }
      }
      break;

    default: INVALID_ARGUMENT_EXCEPTION();  return;
    }

    file_handle_ = ::CreateFile(
                      fname, 
                      desired_access,
                      share_mode,
                      0, // &security_attributes
                      creation_distribution,
                      flags_and_attributes,
                      0  // template_handle
                   );

    if (file_handle_ == INVALID_HANDLE_VALUE) {
      DWORD err = ::GetLastError();
      API_FAILED_EXCEPTION( err );
      return;
    }

    eof_ = false;

  }

  //===========================================================================
  //! デストラクタ
  ~FileDescriptor() {
    if ( file_handle_ != INVALID_HANDLE_VALUE ) {
      ::CloseHandle(file_handle_);
    }
  }


  //===========================================================================
  /*! 現在の読み込み位置を取得する
   * @exception XAPIFailed  ::SetFilePointer が失敗
   */
  long getPosition(long *high = 0) {
    if (high) *high = 0;
    DWORD low = ::SetFilePointer(file_handle_, 0, high, FILE_BEGIN);
    if (low == INVALID_SET_FILE_POINTER) {
      DWORD err = ::GetLastError();
      if (err != 0) {
        API_FAILED_EXCEPTION( err );
      }
    }
    return (long)low;
  }

  //===========================================================================
  /*! 現在の読み込み位置を設定する
   * @param origin   FROM_BEGIN. FROM_HERE, FROM_END のいづれか
   * @param low     位置を示す下位 32 ビット 
   * @param high    上位32ビット -- 省略時は 0
   * @exception  XAPIFailed    API が失敗
   */
  void setPosition( Origin origin, long low, long high = 0 ) {
    DWORD move_method = FILE_BEGIN;

    switch (origin) {
    case FROM_BEGIN :
      break;
    case FROM_HERE:
      move_method = FILE_CURRENT;
      break;
    case FROM_END :
      move_method = FILE_END;
      break;
    }

    DWORD r = ::SetFilePointer(file_handle_, low, &high, move_method);
    if (r == INVALID_SET_FILE_POINTER) {
      DWORD err = ::GetLastError();
      if (err != 0) {
        API_FAILED_EXCEPTION( err );
      }
    }
  }

  //===========================================================================
  /*! ファイルから読む
   * @param buf  バッファへのポインタ
   * @param size バッファのサイズ
   * @return  実際に読み込んだサイズを返す
   * @exception XAPIFailed
   */
  unsigned long read(void *buf, unsigned long size) {
    DWORD readbytes = 0;

    if (file_handle_ == INVALID_HANDLE_VALUE) {
      ASSERTION_FAILED_EXCEPTION();
      return 0;
    }

    BOOL r = ::ReadFile(file_handle_, buf, size, &readbytes, NULL );
    if (!r) {
      DWORD err = ::GetLastError();
      if (err == ERROR_HANDLE_EOF) {
        eof_ = true;
        return 0;
      }
      API_FAILED_EXCEPTION( err );
      return 0;
    }

    return (unsigned long)readbytes;
  }

  //===========================================================================
  /*! ファイルに書き込む
   * @param buf  書き込むバッファ
   * @param size 書き込むサイズ
   * @retval 実際に書き込んだサイズ
   *
   * @exception XAssertionFailed  オブジェクトが正常に生成できていない
   * @exception XAPIFailed        API 失敗
   */
  unsigned int write(const void *buf, unsigned long size) {
    DWORD wrote = 0;

    if (file_handle_ == INVALID_HANDLE_VALUE) {
      ASSERTION_FAILED_EXCEPTION();
      return 0;
    }

    BOOL r = ::WriteFile( file_handle_, buf, size, &wrote, NULL );
      if (!r) {
        DWORD err = ::GetLastError();
        API_FAILED_EXCEPTION( err );
      }

    return wrote;
  }

  //===========================================================================
  //! ファイルハンドルを得る
  FileHandle getHandle() {
    return file_handle_;
  }

  // operator void * () const {} // 作れない
};


#undef INVALID_ARGUMENT_EXCEPTION
#undef API_FAILED_EXCEPTION
#undef ASSERTION_FAILED_EXCEPTION

#endif // FILEDESC_H


