// Here we set up transformations.  These are just examples, set up so
// that our unit tests can transform among spaces in ways that we will
// recognize as correct.  The "shader" and "object" spaces are required
// by OSL and the ShaderGlobals will need to have references to them.
// For good measure, we also set up a "myspace" space, registering it
// with the RendererServices.
// 
static void
setup_transformations (SimpleRenderer &rend, OSL::Matrix44 &Mshad,
                       OSL::Matrix44 &Mobj)
{
    Matrix44 M (1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
    rend.camera_params (M, ustring("perspective"), 90.0f,
                        0.1f, 1000.0f, xres, yres);

    // Make a "shader" space that is translated one unit in x and rotated
    // 45deg about the z axis.
    Mshad.makeIdentity ();
    Mshad.translate (OSL::Vec3 (1.0, 0.0, 0.0));
    Mshad.rotate (OSL::Vec3 (0.0, 0.0, M_PI_4));
    // std::cout << "shader-to-common matrix: " << Mshad << "\n";

    // Make an object space that is translated one unit in y and rotated
    // 90deg about the z axis.
    Mobj.makeIdentity ();
    Mobj.translate (OSL::Vec3 (0.0, 1.0, 0.0));
    Mobj.rotate (OSL::Vec3 (0.0, 0.0, M_PI_2));
    // std::cout << "object-to-common matrix: " << Mobj << "\n";

    OSL::Matrix44 Mmyspace;
    Mmyspace.scale (OSL::Vec3 (1.0, 2.0, 1.0));
    // std::cout << "myspace-to-common matrix: " << Mmyspace << "\n";
    rend.name_transform ("myspace", Mmyspace);
}
int
main (int argc, const char *argv[])
{
    // Create a new shading system.
    Timer timer;
    SimpleRenderer rend;
    shadingsys = ShadingSystem::create (&rend, NULL, &errhandler);
    shadingsys->attribute("lockgeom", 1);

    shadingsys->ShaderGroupBegin ();
    getargs (argc, argv);

    if (debug || verbose)
        errhandler.verbosity (ErrorHandler::VERBOSE);

    for (size_t i = 0;  i < connections.size();  i += 4) {
        if (i+3 < connections.size()) {
            std::cout << "Connect " 
                      << connections[i] << "." << connections[i+1]
                      << " to " << connections[i+2] << "." << connections[i+3]
                      << "\n";
            shadingsys->ConnectShaders (connections[i].c_str(),
                                        connections[i+1].c_str(),
                                        connections[i+2].c_str(),
                                        connections[i+3].c_str());
        }
    }

    shadingsys->ShaderGroupEnd ();

    // getargs called 'add_shader' for each shader mentioned on the command
    // line.  So now we should have a valid shading state.
    ShadingAttribStateRef shaderstate = shadingsys->state ();

    // Set up shader globals and a little test grid of points to shade.
    ShaderGlobals shaderglobals;
    memset(&shaderglobals, 0, sizeof(ShaderGlobals));

    // Make a shader space that is translated one unit in x and rotated
    // 45deg about the z axis.
    OSL::Matrix44 Mshad;
    Mshad.translate (OSL::Vec3 (1.0, 0.0, 0.0));
    Mshad.rotate (OSL::Vec3 (0.0, 0.0, M_PI_4));
    // std::cout << "shader-to-common matrix: " << Mshad << "\n";
    OSL::TransformationPtr Mshadptr (&Mshad);
    shaderglobals.shader2common = Mshadptr;

    // Make an object space that is translated one unit in y and rotated
    // 90deg about the z axis.
    OSL::Matrix44 Mobj;
    Mobj.translate (OSL::Vec3 (0.0, 1.0, 0.0));
    Mobj.rotate (OSL::Vec3 (0.0, 0.0, M_PI_2));
    // std::cout << "object-to-common matrix: " << Mobj << "\n";
    OSL::TransformationPtr Mobjptr (&Mobj);
    shaderglobals.object2common = Mobjptr;

    // Make a 'myspace that is non-uniformly scaled
    OSL::Matrix44 Mmyspace;
    Mmyspace.scale (OSL::Vec3 (1.0, 2.0, 1.0));
    // std::cout << "myspace-to-common matrix: " << Mmyspace << "\n";
    rend.name_transform ("myspace", Mmyspace);

    shaderglobals.dudx = 1.0f / xres;
    shaderglobals.dvdy = 1.0f / yres;

    shaderglobals.raytype = ((ShadingSystemImpl *)shadingsys)->raytype_bit (ustring(raytype));

    double setuptime = timer ();
    double runtime = 0;

    std::vector<float> pixel;

    if (outputfiles.size() != 0)
        std::cout << "\n";

    // grab this once since we will be shading several points
    ShadingSystemImpl *ssi = (ShadingSystemImpl *)shadingsys;
    void* thread_info = ssi->create_thread_info();
    for (int iter = 0;  iter < iters;  ++iter) {
        for (int y = 0, n = 0;  y < yres;  ++y) {
            for (int x = 0;  x < xres;  ++x, ++n) {
                shaderglobals.u = (xres == 1) ? 0.5f : (float) x / (xres - 1);
                shaderglobals.v = (yres == 1) ? 0.5f : (float) y / (yres - 1);
                shaderglobals.P = Vec3 (shaderglobals.u, shaderglobals.v, 1.0f);
                shaderglobals.dPdx = Vec3 (shaderglobals.dudx, shaderglobals.dudy, 0.0f);
                shaderglobals.dPdy = Vec3 (shaderglobals.dvdx, shaderglobals.dvdy, 0.0f);
                shaderglobals.N    = Vec3 (0, 0, 1);
                shaderglobals.Ng   = Vec3 (0, 0, 1);
                shaderglobals.dPdu = Vec3 (1.0f, 0.0f, 0.0f);
                shaderglobals.dPdv = Vec3 (0.0f, 1.0f, 0.0f);
                shaderglobals.surfacearea = 1;

                // Request a shading context, bind it, execute the shaders.
                // FIXME -- this will eventually be replaced with a public
                // ShadingSystem call that encapsulates it.
                ShadingContext *ctx = ssi->get_context (thread_info);
                timer.reset ();
                timer.start ();
                // run shader for this point
                ctx->execute (ShadUseSurface, *shaderstate, shaderglobals);
                runtime += timer ();

                if (iter == (iters - 1)) {
                   // extract any output vars into images (on last iteration only)
                   for (size_t i = 0;  i < outputfiles.size();  ++i) {
                       Symbol *sym = ctx->symbol (ShadUseSurface, ustring(outputvars[i]));
                       if (! sym) {
                           if (n == 0) {
                              std::cout << "Output " << outputvars[i] << " not found, skipping.\n";
                              outputimgs.push_back(0); // invalid image
                           }
                           continue;
                       }
                       if (n == 0)
                           std::cout << "Output " << outputvars[i] << " to " << outputfiles[i]<< "\n";
                       TypeDesc t = sym->typespec().simpletype();
                       TypeDesc tbase = TypeDesc ((TypeDesc::BASETYPE)t.basetype);
                       TypeDesc outtypebase = tbase;
                       if (dataformatname == "uint8")
                           outtypebase = TypeDesc::UINT8;
                       else if (dataformatname == "half")
                           outtypebase = TypeDesc::HALF;
                       else if (dataformatname == "float")
                           outtypebase = TypeDesc::FLOAT;
                       int nchans = t.numelements() * t.aggregate;
                       pixel.resize (nchans);
                       if (n == 0) {
                           OIIO::ImageSpec spec (xres, yres, nchans, outtypebase);
                           OIIO::ImageBuf* img = new OIIO::ImageBuf(outputfiles[i], spec);
#if OPENIMAGEIO_VERSION >= 900 /* 0.9.0 */
                           OIIO::ImageBufAlgo::zero (*img);
#else
                           img->zero ();
#endif
                           outputimgs.push_back(img);
                       }
                       OIIO::convert_types (tbase, ctx->symbol_data (*sym, 0),
                                                   TypeDesc::FLOAT, &pixel[0], nchans);
                       outputimgs[i]->setpixel (x, y, &pixel[0]);
                   }
                }
                ssi->release_context (ctx, thread_info);
            }
        }
    }
    ssi->destroy_thread_info(thread_info);

    if (outputfiles.size() == 0)
        std::cout << "\n";

    // write any images to disk
    for (size_t i = 0;  i < outputimgs.size();  ++i) {
        if (outputimgs[i]) {
           outputimgs[i]->save();
            delete outputimgs[i];
        }
    }
    if (debug || stats) {
        std::cout << "\n";
        std::cout << "Setup: " << Strutil::timeintervalformat (setuptime,2) << "\n";
        std::cout << "Run  : " << Strutil::timeintervalformat (runtime,2) << "\n";
        std::cout << "\n";
        std::cout << shadingsys->getstats (5) << "\n";
    }

    ShadingSystem::destroy (shadingsys);
    return EXIT_SUCCESS;
}