// ---------------------------------------------------------------------------
// - Cubmap.cpp                                                              -
// - afnix:dip service - cubmap class implementation                         -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Dipsid.hxx"
#include "Vector.hpp"
#include "Cubmap.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // Notes on the cubmap image layout
  // The cubmap layout is automatically detected from the image aspect ratio.
  // 6:1 +X -X +Y -Y +Z -Z  3:2 +X +Y +Z                  1:6 +X
  //                            -X -Y -Z                      -X
  // 3:4    +Y              4:3    +Y         2:3 +X -X       +Y
  //     +Z +X -Z               -X +Z +X -Z       +Y -Y       -Y
  //        -Y                     -Y             +Z -Z       +Z
  //        -X                                                -Z

  // the cubmap
  enum t_cmil : long
    {
      CMIL_AR16 = 0L, // 1:6 ratio
      CMIL_AR61 = 1L, // 6:1 ratio
      CMIL_AR32 = 2L, // 3:2 ratio
      CMIL_AR23 = 3L, // 2:3 ratio
      CMIL_AR34 = 4L, // 3:4 ratio
      CMIL_AR43 = 5L, // 4:3 ratio
      CMIL_GR32 = 6L, // 3:2 image
      CMIL_GR23 = 7L, // 2:3 image
    };

  // the face normalized left x position
  static t_real FNLX_CMIL[8][6] = {
    { 0.0,     1.0/6.0, 1.0/3.0, 0.5,     2.0/3.0, 5.0/6.0 }, // AR16
    { 0.0,     0.0,     0.0,     0.0,     0.0,     0.0     }, // AR61
    { 0.0,     0.0,     1.0/3.0, 1.0/3.0, 2.0/3.0, 2.0/3.0 }, // AR32
    { 0.0,     0.5,     0.0,     0.5,     0.0,     0.5     }, // AR23
    { 1.0/3.0, 1.0/3.0, 1.0/3.0, 1.0/3.0, 0.0,     2.0/3.0 }, // AR34
    { 0.5,     0.0,     1.0/4.0, 1.0/4.0, 1.0/4.0, 3.0/4.0 }, // AR43
    { 0.0,     1.0/3.0, 2.0/3.0, 0.0,     1.0/3.0, 2.0/3.0 }, // GR32
    { 0.0,     0.5,     0.0,     0.5,     0.0,     0.5     }  // GR23
  };

  // the face normalized right x position
  static t_real FNRX_CMIL[8][6] = {
    { 1.0/6.0, 1.0/3.0, 0.5,     2.0/3.0, 5.0/6.0, 1.0     }, // AR16
    { 1.0,     1.0,     1.0,     1.0,     1.0,     1.0     }, // AR61
    { 1.0/3.0, 1.0/3.0, 2.0/3.0, 2.0/3.0, 1.0,     1.0     }, // AR32
    { 0.5,     1.0,     0.5,     1.0,     0.5,     1.0     }, // AR23
    { 2.0/3.0, 2.0/3.0, 2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0     }, // AR34
    { 3.0/4.0, 1.0/4.0, 0.5,     0.5,     0.5,     1.0     }, // AR43
    { 1.0/3.0, 1.0/3.0, 2.0/3.0, 2.0/3.0, 1.0,     1.0     }, // GR32
    { 0.5,     1.0,     0.5,     1.0,     0.5,     1.0     }  // GR23
  };

  // the face normalized top y position
  static t_real FNTY_CMIL[8][6] = {
    { 1.0,     1.0,     1.0,     1.0,     1.0,     1.0     }, // AR16
    { 1.0,     5.0/6.0, 2.0/3.0, 0.5,     1.0/3.0, 1.0/6.0 }, // AR61
    { 1.0,     0.5,     1.0,     0.5,     1.0,     0.5     }, // AR32
    { 1.0,     1.0,     2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0 }, // AR23
    { 3.0/4.0, 1.0/4.0, 1.0,     0.5,     3.0/4.0, 3.0/4.0 }, // AR34
    { 2.0/3.0, 2.0/3.0, 1.0,     1.0/3.0, 2.0/3.0, 2.0/3.0 }, // AR43
    { 1.0,     0.5,     1.0,     0.5,     1.0,     0.5     }, // GR32
    { 1.0,     1.0,     2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0 }  // GR23
  };

  // the face normalized bottom y position
  static t_real FNBY_CMIL[8][6] = {
    { 0.0,     0.0,     0.0,     0.0,     0.0,     0.0     }, // AR16
    { 5.0/6.0, 2.0/3.0, 0.5,     1.0/3.0, 1.0/6.0, 0.0     }, // AR61
    { 0.5,     0.0,     0.5,     0.0,     0.5,     0.0     }, // AR32
    { 2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0, 0.0,     0.0     }, // AR23
    { 0.5,     0.0,     3.0/4.0, 1.0/4.0, 0.5,     0.5     }, // AR34
    { 1.0/3.0, 1.0/3.0, 2.0/3.0, 0.0,     1.0/3.0, 1.0/3.0 }, // AR43
    { 0.5,     0.0,     0.5,     0.0,     0.5,     0.0     }, // GR32
    { 2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0, 0.0,     0.0     }  // GR23
  };
  
  // this procedure computes the cubmap layout by geometry
  static t_cmil geom_to_cmil (const long wdth, const long hght) {
    // check for valid geometry
    if ((wdth <= 0L) || (hght <= 0L)) {
      throw Exception ("cubmap-error", "invalid nil geometry");
    }
    if (wdth > hght) {
      // check for 6:1
      if ((1L * wdth) == (6L * hght)) return CMIL_AR61;
      // check for 3:2
      if ((2L * wdth) == (3L * hght)) return CMIL_AR32;
      // check for 4:3
      if ((3L * wdth) == (4L * hght)) return CMIL_AR43;
    }
    if (wdth < hght) {
      // check for 6:1
      if ((6L * wdth) == (1L * hght)) return CMIL_AR16;
      // check for 2:3
      if ((3L * wdth) == (2L * hght)) return CMIL_AR23;
      // check for 3:4
      if ((4L * wdth) == (3L * hght)) return CMIL_AR34;
    }
    // assume here 3:2 or 2:3 geometry layout
    return (wdth < hght) ? CMIL_GR23 : CMIL_GR32;
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a cubmap by pixmap

  Cubmap::Cubmap (Pixmap* pixm) {
    if (pixm != nullptr) {
      Pixmap::operator = (static_cast<Pixmap&&>(*pixm));
    }
  }
  
  // create a cubmap by pixmap

  Cubmap::Cubmap (const Pixmap& pixm) {
    pixm.rdlock ();
    try {
      Pixmap::operator = (pixm);
      pixm.unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // copy construct this cubmap

  Cubmap::Cubmap (const Cubmap& that) {
    that.rdlock ();
    try {
      // copy the base pixmap
      Pixmap::operator = (that);
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }  
  }

  // move construct this cubmap

  Cubmap::Cubmap (Cubmap&& that) noexcept {
    that.wrlock ();
    try {
      // copy move the base pixmap
      Pixmap::operator = (static_cast<Pixmap&&>(that));
      that.unlock ();
    } catch (...) {
      that.unlock ();
    }
  }

  // assign an cubmap to this one

  Cubmap& Cubmap::operator = (const Cubmap& that) {
    // check for self-move
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // assign the pixmap
      Pixmap::operator = (that);
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // move an cubmap to this one

  Cubmap& Cubmap::operator = (Cubmap&& that) noexcept {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.wrlock ();
    try {
      // move the base pixmap
      Pixmap::operator = (static_cast<Pixmap&&>(that));
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      return *this;
    }
  }

  // return the class name

  String Cubmap::repr (void) const {
    return "Cubmap";
  }

  // return a clone of this object

  Object* Cubmap::clone (void) const {
    return new Cubmap (*this);
  }
  
  // return the serial did
  
  t_word Cubmap::getdid (void) const {
    return SRL_DEOD_DIP;
  }

  // return the serial sid
  
  t_word Cubmap::getsid (void) const {
    return SRL_CUBM_SID;
  }

  // serialize this cubmap

  void Cubmap::wrstream (OutputStream& os) const {
    rdlock ();
    try {
      // serialize the pixmap
      Pixmap::wrstream (os);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // deserialize this cubmap

  void Cubmap::rdstream (InputStream& is) {
    wrlock ();
    try {
      // deserialize the cubmap
      Pixmap::rdstream (is);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the face normalized left x position

  t_real Cubmap::getfnlx (const t_face face) const {
    rdlock ();
    try {
      // check for nil
      if ((d_wdth <= 0L) || (d_hght <= 0L)) {
	unlock ();
	return 0.0;
      }
      // get the image layout by geometry
      t_cmil cmil = geom_to_cmil (d_wdth, d_hght);
      // get the top-left normalized position
      t_real result = FNLX_CMIL[cmil][face];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the face normalized right x position

  t_real Cubmap::getfnrx (const t_face face) const {
    rdlock ();
    try {
      // check for nil
      if ((d_wdth <= 0L) || (d_hght <= 0L)) {
	unlock ();
	return 0.0;
      }
      // get the image layout by geometry
      t_cmil cmil = geom_to_cmil (d_wdth, d_hght);
      // get the top-left normalized position
      t_real result = FNRX_CMIL[cmil][face];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the face normalized top y position

  t_real Cubmap::getfnty (const t_face face) const {
    rdlock ();
    try {
      // check for nil
      if ((d_wdth <= 0L) || (d_hght <= 0L)) {
	unlock ();
	return 0.0;
      }
      // get the image layout by geometry
      t_cmil cmil = geom_to_cmil (d_wdth, d_hght);
      // get the top-left normalized position
      t_real result = FNTY_CMIL[cmil][face];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the face normalized bottom y position

  t_real Cubmap::getfnby (const t_face face) const {
    rdlock ();
    try {
      // check for nil
      if ((d_wdth <= 0L) || (d_hght <= 0L)) {
	unlock ();
	return 0.0;
      }
      // get the image layout by geometry
      t_cmil cmil = geom_to_cmil (d_wdth, d_hght);
      // get the top-left normalized position
      t_real result = FNBY_CMIL[cmil][face];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // create a new object in a generic way
  
  Object* Cubmap::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new Cubmap;
    // check for 1 argument
    if (argc == 1) {
      Object* obj = argv->get(0);
      auto pixm = dynamic_cast<Pixmap*>(obj);
      if (pixm == nullptr) {
	throw Exception ("type-error", "invalid object as pixmap",
			 Object::repr (obj));
      }
      return new Cubmap (pixm);
    }
    // invalid arguments
    throw Exception ("argument-error", 
                     "invalid arguments with with cubmap constructor"); 
  }
}
