//#####################################################################
// Copyright 2020.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################

#include <Core/Geometry_Particles/DEBUG_PARTICLES.h>
#include <Core/Grids/CELL_ITERATOR.h>
#include <Core/Grids/EDGE_ITERATOR.h>
#include <Core/Grids/FACE_ITERATOR.h>
#include <Core/Grids/GRID.h>
#include <Core/Grids/NODE_ITERATOR.h>
#include <Core/Log/LOG.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include <Core/Vectors/VECTOR.h>
#include <Core/Viewer/VIEWER_OUTPUT.h>
#include "OP_COUNT.h"

using namespace PhysBAM;

// TASK 1: In this part, we are looking at grids, iterating over grids, dumping
// output for the viewer, using the viewer, and debug particles.  Classes:
// VIEWER_DIR, VIEWER_OUTPUT, FACE_ITERATOR, CELL_ITERATOR, NODE_ITERATOR,
// EDGE_ITERATOR, FACE_RANGE_ITERATOR, RANGE_ITERATOR.  The routines in
// DEBUG_PARTICLES.h are also introduced here.

// Note that there is a viewer for 1D, 2D, and 3D.  They are located in
// Projects/opengl_[123]d.  The viewers are run by passing in the output
// directory as their argument.  You will use these programs frequently; it is
// worth having convenient aliases for them.  I use aliases o2 and o3 for the 2D
// and 3D viewers.  I also alias o to the 3D version.

int main(int argc, char* argv[])
{
    // These three typedefs are idiomatic for PhysBAM.
    typedef double T;
    typedef VECTOR<T,2> TV;
    typedef VECTOR<int,2> IV;

    std::string dir="output_5";

    // VIEWER_DIR is responsible for the layout of the output directory.  The
    // output has two main parts: common and frame directories.  The common
    // directory typically holds log.txt (a copy of the stdout), last_frame (how
    // many frames were dumped), and data that is not changing (such as the
    // grid).
    VIEWER_DIR viewer_dir(dir);
    VIEWER_OUTPUT viewer_output(Stream_Type(0.),viewer_dir);
    viewer_output.Add("debug_particles",DEBUG_PARTICLES<TV>::Get(),false,{});

    // Be aware of the data that GRID and RANGE store for now.  We will get back
    // to them in a later part.  For now we are mostly just using them.  The
    // first argument tells us the resolution (4 grid cells in each direction).
    // The second argument is the spatial domain of the grid (-2..2 in each
    // direction), and the true means we want a MAC grid (positions naturally
    // live at cell centers, and the resolution refers to grid cells).
    GRID<TV> grid(IV()+4,RANGE<TV>::Centered_Box()*2);

    // Adds an object for VIEWER_OUTPUT to dump out in the common directory.
    // The first argument is the filename (eg, output/common/grid.gz).  The
    // output data will be written compressed (*.gz) by default.  Output is
    // typically written in binary, which means float/double matters.  Be sure
    // to write consistently in floats or doubles.  The viewer can handle either
    // one automatically.  Note that the name of the grid is important; the
    // viewer will look for it here.
    viewer_output.Add("grid",grid,true,{.draw=(TV::m==2)});

    // Dumps a frame without a title.
    Flush_Frame();

    // Dumps a frame with a title.  Titles help identify what you are looking at
    // in the viewer.
    Flush_Frame("Title goes here");

    // We will give names to simple colors; normally these would be hardcoded in
    // place.
    VECTOR<T,3> r(1,0,0),g(0,1,0),b(0,0,1),y(1,1,0),c(0,1,1),m(1,0,1),w(1,1,1),k;

    // Debug objects are ad-hoc objects that we can send to the viewer.  We use
    // them mostly for debugging to identify spatial relationships.  For
    // example, if we write a routine to compute a collision location, we can
    // debug this by dumping a debug object at that location and check to see if
    // the object is drawn at the intersection location.
    Add_Point(TV(-1, 1),{1,1,1});
    Add_Ball(TV( 0, 1),.4,{1,1,1});
    Add_Regular_Polygon(TV( 1, 1),.4,3,0,{1,1,1});
    Add_Regular_Polygon(TV(-1, 0),.4,3,2,{1,1,1});
    Add_Regular_Polygon(TV( 0, 0),.4,3,4,{1,1,1});
    Add_Regular_Polygon(TV( 1, 0),.4,3,6,{1,1,1});
    Add_Regular_Polygon(TV(-1,-1),.4,4,0,{1,1,1});
    Add_Regular_Polygon(TV( 0,-1),.4,4,1,{1,1,1});
    Add_Regular_Polygon(TV( 1,-1),.4,5,0,{1,1,1});
    Flush_Frame("Points with markings");

    // Note that debug objects are cleared when the frame is flushed.  Next,
    // lets introduce colors, more complicated markings, and arrows (for
    // visualizing vector quantities like force or velocity).
    Add_Point(TV(-1, 1),r);
    Add_Point(TV( 0, 1),g);
    Add_Point(TV( 1, 1),b);
    Add_Point(TV(-1, 0),{1,1,1});
    Add_Ball(TV(-1, 0),.4,{1,1,1});
    Add_Regular_Polygon(TV(-1, 0),.4,3,0,{1,1,1});
    Add_Point(TV( 0, 0),r);
    Add_Ball(TV( 0, 0),.4,g);
    Add_Regular_Polygon(TV( 0, 0),.4,3,0,b);
    Add_Arrow(TV( 1, 0),TV(.2,.4),r);
    Add_Point(TV(-1,-1),{1,1,1});
    Add_Arrow(TV(-1,-1),TV(.2,.4),r,true,false);
    Add_Arrow(TV( 0,-1),TV(.2,.4),r);
    Add_Arrow(TV( 0,-1),TV(-.2,.4),g,true,false);
    Add_Point(TV( 0,-1),g);
    Add_Text(TV( 1,-1),"text here",m);
    Flush_Frame("Advanced point markings and colors");

    // Try pressing = and - in the viewer on this frame.  They allow you to
    // scale some types of objects.  This is commonly used for arrows for
    // velocities, which allows you to accommodate velocities with a wide
    // variation in magnitude.
    Add_Segment(TV(-1,1),TV(-1.2,1.2),r);
    Add_Triangle(TV(0,1),TV(0.2,1),TV(0,1.2),g,false);
    Add_Triangle(TV(1,1),TV(1.2,1),TV(1,1.2),g,false);
    Add_Point(TV(-1, 0),{1,1,1});
    Add_Regular_Polygon(TV(-1, 0),.4,3,0,{1,1,1});
    Add_Triangle(TV(0,0),TV(0.2,0),TV(0,0.2),g);
    Add_Point(TV( 1, 0),r);
    Add_Arrow(TV( 1, 0),TV()+.5,r);
    Add_Point(TV(-1,-1),g);
    Add_Arrow(TV(-1,-1),TV()+.5,g);
    Add_Triangle(TV(0,-1),TV(0.2,-1),TV(0,-1+.2),g);
    Add_Triangle(TV(0,-1),TV(0.2,-1),TV(0,-1-.2),r);
    Add_Ball(TV(1,-1),.5,{1,1,1});
    Add_Regular_Polygon(TV(1,-1),.4,3,0,{1,1,1});
    Add_Ball(TV(1,-1),.4,y);
    Flush_Frame("Lines and polygons");

    // Try pressing = and - in the viewer on this frame.  They allow you to
    // scale some types of objects.  This is commonly used for arrows for
    // velocities, which allows you to accommodate velocities with a wide
    // variation in magnitude.
    Add_Ball(TV(-1, 0),.5,{1,1,1});
    Add_Point(TV(1, 0),y);
    Add_Arrow(TV(1, 0),TV()+(T).5,r);
    Add_Arrow(TV(1, 0),TV()-(T).2,g);
    Add_Regular_Polygon(TV(1, 0),.3,6,0,b);
    Flush_Frame("Shift chick the objects");

    // For iterating over grids, we have a variety of iterators.  These are the
    // high level iterators, along with some of their variations.  Note that
    // they are templatized over TV and take the grid as input.  Most of them
    // support a Location() function to produce a spacial location.
    for(CELL_ITERATOR<TV> it(grid);it.Valid();it.Next())
        Add_Point(it.Location(),r);
    Flush_Frame("iterate cells");

    for(CELL_ITERATOR<TV> it(grid,2,GHOST_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),r);
    Flush_Frame("iterate cells ghost");

    for(CELL_ITERATOR<TV> it(grid,2,BOUNDARY_INTERIOR_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),r);
    Flush_Frame("iterate cells boundary-interior");

    for(NODE_ITERATOR<TV> it(grid);it.Valid();it.Next())
        Add_Point(it.Location(),g);
    Flush_Frame("iterate nodes");

    for(NODE_ITERATOR<TV> it(grid,2,WHOLE_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),g);
    Flush_Frame("iterate nodes whole");

    for(NODE_ITERATOR<TV> it(grid,2,GHOST_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),g);
    Flush_Frame("iterate nodes ghost");

    for(NODE_ITERATOR<TV> it(grid,2,BOUNDARY_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),g);
    Flush_Frame("iterate nodes boundary");

    for(NODE_ITERATOR<TV> it(grid,2,INTERIOR_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),g);
    Flush_Frame("iterate nodes interior");

    for(FACE_ITERATOR<TV> it(grid);it.Valid();it.Next())
        Add_Point(it.Location(),it.Axis()?g:m);
    Flush_Frame("iterate faces");

    for(FACE_ITERATOR<TV> it(grid,2,WHOLE_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),it.Axis()?g:m);
    Flush_Frame("iterate faces whole");

    for(FACE_ITERATOR<TV> it(grid,2,GHOST_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),it.Axis()?g:m);
    Flush_Frame("iterate faces ghost");

    for(FACE_ITERATOR<TV> it(grid,2,BOUNDARY_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),it.Axis()?g:m);
    Flush_Frame("iterate faces boundary");

    for(FACE_ITERATOR<TV> it(grid,2,INTERIOR_REGION);it.Valid();it.Next())
        Add_Point(it.Location(),it.Axis()?g:m);
    Flush_Frame("iterate faces interior");

    // Edge iterator is seldom used.
    for(EDGE_ITERATOR<TV> it(grid);it.Valid();it.Next())
    {
        auto e=it.Full_Index();
        Add_Segment(grid.Node_X(e.First_Node_Index()),grid.Node_X(e.Second_Node_Index()),r);
    }
    Flush_Frame("iterate edges");

    // These are mostly built on two low-level iterators.  These are
    // integer-only and do not take in grid and do not support the function
    // Location().  They allow for very precise and fine-tuned iteration over
    // points.  RANGE_ITERATOR is also very useful for iterating over
    // multidimensional arrays, which do not have a notion of space.
    for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices());it.Valid();it.Next())
        Add_Point(grid.Cell_X(it.index),r);
    Flush_Frame("range iterator");
    
    for(RANGE_ITERATOR<TV::m> it(grid.Node_Indices());it.Valid();it.Next())
        Add_Point(grid.Node_X(it.index),r);
    Flush_Frame("range iterator (nodes)");
    
    for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost);it.Valid();it.Next())
        Add_Point(grid.Cell_X(it.index),r);
    Flush_Frame("range iterator ghost");

    VECTOR<T,3> side_colors[4]={r,g,b,y};
    for(int side=0;side<TV::m*2;side++)
        for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost,side);it.Valid();it.Next())
            Add_Point(grid.Cell_X(it.index),side_colors[side]);
    Flush_Frame("range iterator ghost side");

    for(int side=0;side<TV::m*2;side++)
        for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost|RI::omit_corners,side);it.Valid();it.Next())
            Add_Point(grid.Cell_X(it.index),side_colors[side]);
    Flush_Frame("range iterator ghost side omit-corners");

    for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost);it.Valid();it.Next())
        Add_Point(grid.Cell_X(it.index),side_colors[it.side]);
    Flush_Frame("range iterator ghost");

    for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost|RI::delay_corners);it.Valid();it.Next())
        Add_Point(grid.Cell_X(it.index),side_colors[it.side]);
    Flush_Frame("range iterator ghost delay-corners");

    for(RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RI::ghost|RI::side_mask,6);it.Valid();it.Next())
        Add_Point(grid.Cell_X(it.index),side_colors[it.side]);
    Flush_Frame("range iterator ghost side-mask");

    // FACE_RANGE_ITERATOR is the low-level iterator for faces.
    for(FACE_RANGE_ITERATOR<TV::m> it(grid.Cell_Indices());it.Valid();it.Next())
        Add_Point(grid.Face_X(it.face),side_colors[it.side]);
    Flush_Frame("face range iterator");

    for(FACE_RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RF::ghost);it.Valid();it.Next())
        Add_Point(grid.Face_X(it.face),side_colors[it.side]);
    Flush_Frame("face range iterator ghost");

    for(FACE_RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RF::ghost|RF::skip_inner);it.Valid();it.Next())
        Add_Point(grid.Face_X(it.face),side_colors[it.side]);
    Flush_Frame("face range iterator ghost skip-inner");

    for(FACE_RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RF::ghost|RF::skip_outer);it.Valid();it.Next())
        Add_Point(grid.Face_X(it.face),side_colors[it.side]);
    Flush_Frame("face range iterator ghost skip-outer");

    for(FACE_RANGE_ITERATOR<TV::m> it(grid.Cell_Indices(),1,-1,RF::ghost|RF::delay_corners);it.Valid();it.Next())
        Add_Point(grid.Face_X(it.face),side_colors[it.side]);
    Flush_Frame("face range iterator ghost delay-corners");

    // Now that we have introduced the basics, it is time for you to practice
    // them yourself. For each task below, reproduce the same output that the
    // solution produces.  Note that your output goes in output_5, and the
    // solution output goes in output_5_sol.  View both side by side to get them
    // to match.  Note that you may require many steps to produce the results.

    // SOL
    Flush_Frame("TASK 2");

    // SOL
    Flush_Frame("TASK 3");

    // SOL
    Flush_Frame("TASK 4");

    // SOL
    Flush_Frame("TASK 5");

    // SOL
    Flush_Frame("TASK 6");

    // SOL
    Flush_Frame("TASK 7");

    // SOL
    Flush_Frame("TASK 8");

    // SOL
    Flush_Frame("TASK 9");

    return 0;
}
