:- module proj.
% the main module for the 380 project

:- interface.

:- import_module io.

:- pred main(io__state::di, io__state::uo) is det.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- implementation.

:- import_module list, int, float, char, string, require, gfx, scene, vec3, ray, std_util, ppm, debug.
:- import_module prolog.

% processed program arguments
:- type argtable --->
        argtable(
                float,              % ambient illumination
                list(light),        % list of light sources
                float,              % coefficient of specular reflection
                float,              % exponent of specular reflection
                float,              % ``focal length'' of the pin-hole camera
                shade_type,         % full or phong
                string,             % scene description file
                background          % background of image
        ).

main -->
    io__command_line_arguments(Args),
    { ArgTableDefault = argtable(0.0, [], 0.0, 1.0, 1.0, full, "-", solid(100, 100, colour(0.0, 0.0, 0.0))) },
    { ArgTable0 = process_args(Args, ArgTableDefault) },
    { argtable(Amb, Light, SpecC, SpecE, Foc, Shad, SceneFile, Background) = ArgTable0 },
    { (
        % if no light source given, use default -- unit power light at origin
        Light = []
    ->
        Light1 = [light(1.0, vec(0.0, 0.0, 0.0))]
    ;
        Light1 = Light
    ) },
    { ArgTable = argtable(Amb, Light1, SpecC, SpecE, Foc, Shad, SceneFile, Background) },
    get_scene(SceneFile, Scene),
    % { dump("got scene\n", []) },
    % { dump_scene(Scene) },
    get_image_size(Background, X, Y),
    % { dump("got size\n", []) },
    prolog__tell("OUT.ppm", _),
    ppm_write_header(X, Y),
    % { dump("written header\n", []) },
    generate_image(0, X, 0, Y, ArgTable, Scene).

% process_args(ArgList, Default) = ArgTable
% process the argument list
:- func process_args(list(string), argtable) = argtable.
:- mode process_args(in, in) = out is det.

process_args(L, T0) = T :-
    (
        L = [],
        T = T0
    ; 
        L = [S | L0],
        T0 = argtable(Amb, LL, SC, SE, Foc, Shad, Scene, Back),
            (
                S = "-a"    % ambient illumination
            ->
                (
                    L0 = [S1 | L1],
                    string__to_float(S1, Amb1)
                ->
                    T = process_args(L1, argtable(Amb1, LL, SC, SE, Foc, Shad, Scene, Back))
                ;
                    error("Wrong Arguments to -a option")
                )
            ;
                S = "-l"    % light source
            ->
                (
                    L0 = [S1, S2, S3, S4 | L1],
                    string__to_float(S1, LightPower),
                    string__to_float(S2, LightX),
                    string__to_float(S3, LightY),
                    string__to_float(S4, LightZ)
                ->
                    LL1 = [light(LightPower, vec(LightX, LightY, LightZ)) | LL],
                    T = process_args(L1, argtable(Amb, LL1, SC, SE, Foc, Shad, Scene, Back))
                ;
                    error("Wrong Arguments to -l option")
                )
            ;
                S = "-s"    % specular reflection
            ->
                (
                    L0 = [S1, S2 | L1],
                    string__to_float(S1, SC1),
                    string__to_float(S2, SE1)
                ->
                    T = process_args(L1, argtable(Amb, LL, SC1, SE1, Foc, Shad, Scene, Back))
                ;
                    error("Wrong Arguments to -s option")
                )
            ;
                S = "-f"    % focal length
            ->
                (
                    L0 = [S1 | L1],
                    string__to_float(S1, Foc1)
                ->
                    T = process_args(L1, argtable(Amb, LL, SC, SE, Foc1,Shad, Scene, Back))
                ;
                    error("Wrong Arguments to -f option")
                )
            ;
                S = "-F"    % polyhedral shading
            ->
                T = process_args(L0, argtable(Amb, LL, SC, SE, Foc, full, Scene, Back))
            ;
                S = "-S"    % smooth shading
            ->
                T = process_args(L0, argtable(Amb, LL, SC, SE, Foc, phong, Scene, Back))
            ;
                Scene1 = S,
                    (
                        L0 = [S1, S2, S3, S4, S5 | _L1],
                        string__to_int(S1, Width),
                        string__to_int(S2, Height),
                        string__to_float(S3, BackRed),
                        string__to_float(S4, BackGreen),
                        string__to_float(S5, BackBlue)
                    ->
                        Back1 = solid(Width, Height, colour(BackRed, BackGreen, BackBlue))
                    ;
                        L0 = [S1 | _L1]
                    ->
                        Back1 = filename(S1)
                    ;
                        Back1 = Back
                    ),
                T = argtable(Amb, LL, SC, SE, Foc, Shad, Scene1, Back1)
            )
        ).

% get the dimensions of the image pixmap
:- pred get_image_size(background, int, int, io__state, io__state).
:- mode get_image_size(in, out, out, di, uo) is det.

get_image_size(Background, X, Y) -->
    (
        { Background = filename(_) },
        { X = 100 }, % eventuallly will read from ppm file
        { Y = 100 }
    ;
        { Background = solid(X, Y, _) }
    ).

% return background colour of next pixel
:- pred get_background_colour(background, colour, io__state, io__state).
:- mode get_background_colour(in, out, di, uo) is det.

get_background_colour(Background, Colour) -->
    (
        { Background = filename(_) },
        { Colour = colour(0.0, 0.0, 0.0) } % note: eventually will read from ppm file
    ;
        { Background = solid(_, _, Colour) }
    ).

% generate the image, a pixel at a time
:- pred generate_image(int, int, int, int, argtable, scene, io__state, io__state).
:- mode generate_image(in, in, in, in, in, in, di, uo) is det.

generate_image(X, MaxX, Y, MaxY, ArgTable, Scene) -->
    (
        { Y >= MaxY }
    ->
        []
    ;
        { X >= MaxX }
    ->
        % { dump("Finished Row %d\n", [i(Y)]) },   %% dmo
	% io__report_stats,
        generate_image(0, MaxX, Y+1, MaxY, ArgTable, Scene)
    ;
        { argtable(Amb, Lights, SpecC, SpecE, Foc, Shad, _SceneFile, _Background) = ArgTable },
        { gen_dir(Foc, X, MaxX, Y, MaxY, Dir) },
        { ray_trace(100, vec(0.0, 0.0, 0.0), Dir, Scene, Amb, Lights,
		SpecC, SpecE, Shad, Colour) },
%        get_background_colour(Background, BGCol),
            (
%                { Pixel = yes(Colour) },
                 ppm_write_pixel(Colour)
%            ;
%                { Pixel = no },
%                ppm_write_pixel(BGCol)
            ),
        generate_image(X+1, MaxX, Y, MaxY, ArgTable, Scene)
    ).

% work out the direction of the ray from the origin to the image plane at pixel
% co-ordinates (X, Y).  Image plane is at Z = -Foc in world co-ords.
:- pred gen_dir(float, int, int, int, int, vec3).
:- mode gen_dir(in, in, in, in, in, out) is det.

gen_dir(Foc, X, MaxX, Y, MaxY, Dir) :-
    (
        DZ = -Foc,
        FX = float(X),
        FMaxX = float(MaxX),
        FY = float(Y),
        FMaxY = float(MaxY),
        DX = FX - FMaxX / 2.0,
        DY = -(FY - FMaxY / 2.0),
        %dump("gen_dir: direction=(%f, %f, %f)\n", [f(DX), f(DY), f(DZ)]), %%dmo
        Dir = unit(vec(DX, DY, DZ))
    ).

% the following predicates are for debugging.

:- impure pred dump_scene(scene).
:- mode dump_scene(in) is det.

dump_scene(Scene) :-
    (
        Scene = []
    ;
        Scene = [polytri(Triangles, vec(X, Y, Z), colour(R, G, B)) | Scene1],
        impure dump("Polygon colour (%f, %f, %f)\n", [f(R), f(G), f(B)]),
        impure dump("        normal (%f, %f, %f)\n", [f(X), f(Y), f(Z)]),
        impure dump_triangles(Triangles),
        impure dump_scene(Scene1)
    ).

% debugging stuff.
% print out info about a triangle.
:- impure pred dump_triangles(list(triangle)).
:- mode dump_triangles(in) is det.

dump_triangles(Ts) :-
    (
        Ts = []
    ;
        Ts = [tri(vec(X1, Y1, Z1), vec(X2, Y2, Z2), vec(X3, Y3, Z3), _, _, _) | Ts1],
        impure dump("    (%f, %f, %f)\n    (%f, %f, %f)\n    (%f, %f, %f)\n --------\n", 
            [f(X1), f(Y1), f(Z1), f(X2), f(Y2), f(Z2), f(X3), f(Y3), f(Z3)]),
        impure dump_triangles(Ts1)
    ).
