#!/usr/bin/env python
##########################################################################
# 
# Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
# All Rights Reserved.
# 
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sub license, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice (including the
# next paragraph) shall be included in all copies or substantial portions
# of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# 
##########################################################################


import sys
import gallium
import model
import parser


def make_image(surface):
    pixels = gallium.FloatArray(surface.height*surface.width*4)
    surface.get_tile_rgba(0, 0, surface.width, surface.height, pixels)

    import Image
    outimage = Image.new(
        mode='RGB',
        size=(surface.width, surface.height),
        color=(0,0,0))
    outpixels = outimage.load()
    for y in range(0, surface.height):
        for x in range(0, surface.width):
            offset = (y*surface.width + x)*4
            r, g, b, a = [int(pixels[offset + ch]*255) for ch in range(4)]
            outpixels[x, y] = r, g, b
    return outimage

def save_image(filename, surface):
    outimage = make_image(surface)
    outimage.save(filename, "PNG")

def show_image(surface):
    outimage = make_image(surface)
    
    import Tkinter as tk
    from PIL import Image, ImageTk
    root = tk.Tk()
    
    root.title('background image')
    
    image1 = ImageTk.PhotoImage(outimage)
    w = image1.width()
    h = image1.height()
    x = 100
    y = 100
    root.geometry("%dx%d+%d+%d" % (w, h, x, y))
    panel1 = tk.Label(root, image=image1)
    panel1.pack(side='top', fill='both', expand='yes')
    panel1.image = image1
    root.mainloop()




class Struct:
    """C-like struct"""

    # A basic Python class can pass as a C-like structure
    pass


struct_factories = {
    "pipe_blend_color": gallium.BlendColor,
    "pipe_blend_state": gallium.Blend,
    #"pipe_clip_state": gallium.Clip,
    #"pipe_constant_buffer": gallium.ConstantBuffer,
    "pipe_depth_state": gallium.Depth,
    "pipe_stencil_state": gallium.Stencil,
    "pipe_alpha_state": gallium.Alpha,
    "pipe_depth_stencil_alpha_state": gallium.DepthStencilAlpha,
    "pipe_format_block": gallium.FormatBlock,
    #"pipe_framebuffer_state": gallium.Framebuffer,
    "pipe_poly_stipple": gallium.PolyStipple,
    "pipe_rasterizer_state": gallium.Rasterizer,
    "pipe_sampler_state": gallium.Sampler,
    "pipe_scissor_state": gallium.Scissor,
    #"pipe_shader_state": gallium.Shader,
    #"pipe_vertex_buffer": gallium.VertexBuffer,
    "pipe_vertex_element": gallium.VertexElement,
    "pipe_viewport_state": gallium.Viewport,
    #"pipe_texture": gallium.Texture,
}


member_array_factories = {
    "pipe_rasterizer_state": {"sprite_coord_mode": gallium.ByteArray},                          
    "pipe_poly_stipple": {"stipple": gallium.UnsignedArray},                          
    "pipe_viewport_state": {"scale": gallium.FloatArray, "translate": gallium.FloatArray},                          
    #"pipe_clip_state": {"ucp": gallium.FloatArray},
    "pipe_depth_stencil_alpha_state": {"stencil": gallium.StencilArray},
    "pipe_blend_color": {"color": gallium.FloatArray},
    "pipe_sampler_state": {"border_color": gallium.FloatArray},              
}


class Translator(model.Visitor):
    """Translate model arguments into regular Python objects"""

    def __init__(self, interpreter):
        self.interpreter = interpreter
        self.result = None

    def visit(self, node):
        self.result = None
        node.visit(self)
        return self.result
        
    def visit_literal(self, node):
        self.result = node.value
    
    def visit_named_constant(self, node):
        # lookup the named constant in the gallium module
        self.result = getattr(gallium, node.name)
    
    def visit_array(self, node):
        array = []
        for element in node.elements:
            array.append(self.visit(element))
        self.result = array
    
    def visit_struct(self, node):
        struct_factory = struct_factories.get(node.name, Struct)
        struct = struct_factory()
        for member_name, member_node in node.members:
            member_value = self.visit(member_node)
            try:
                array_factory = member_array_factories[node.name][member_name]
            except KeyError:
                pass
            else:
                assert isinstance(member_value, list)
                array = array_factory(len(member_value))
                for i in range(len(member_value)):
                    array[i] = member_value[i]
                member_value = array
            #print node.name, member_name, member_value
            assert isinstance(struct, Struct) or hasattr(struct, member_name)
            setattr(struct, member_name, member_value)
        self.result = struct
    
    def visit_pointer(self, node):
        self.result = self.interpreter.lookup_object(node.address)


class Object:
    
    def __init__(self, interpreter, real):
        self.interpreter = interpreter
        self.real = real
        

class Global(Object):

    def __init__(self, interpreter, real):
        self.interpreter = interpreter
        self.real = real
        
    def pipe_winsys_create(self):
        return Winsys(self.interpreter, gallium.Device())

    def pipe_screen_create(self, winsys):
        return Screen(self.interpreter, winsys.real)
    
    def pipe_context_create(self, screen):
        context = screen.real.context_create()
        return Context(self.interpreter, context)

    
class Winsys(Object):
    
    def __init__(self, interpreter, real):
        self.interpreter = interpreter
        self.real = real

    def get_name(self):
        pass
    
    def user_buffer_create(self, data, size):
        # We don't really care to distinguish between user and regular buffers
        buffer = self.real.buffer_create(size, 
                                         4, 
                                         gallium.PIPE_BUFFER_USAGE_CPU_READ |
                                         gallium.PIPE_BUFFER_USAGE_CPU_WRITE )
        buffer.write(data, size)
        return buffer
    
    def buffer_create(self, alignment, usage, size):
        return self.real.buffer_create(size, alignment, usage)
    
    def buffer_destroy(self, buffer):
        pass
    
    def buffer_write(self, buffer, data, size):
        buffer.write(data, size)
        
    def fence_finish(self, fence, flags):
        pass
    
    def fence_reference(self, dst, src):
        pass
    
    def flush_frontbuffer(self, surface):
        pass

    def surface_alloc(self):
        return None
    
    def surface_release(self, surface):
        pass


class Screen(Object):
    
    def get_name(self):
        pass
    
    def get_vendor(self):
        pass
    
    def get_param(self, param):
        pass
    
    def get_paramf(self, param):
        pass
    
    def is_format_supported(self, format, target, tex_usage, geom_flags):
        return self.real.is_format_supported(format, target, tex_usage, geom_flags)
    
    def texture_create(self, template):
        return self.real.texture_create(
            format = template.format,
            width = template.width[0],
            height = template.height[0],
            depth = template.depth[0],
            last_level = template.last_level,
            target = template.target,
            tex_usage = template.tex_usage,
        )

    def texture_destroy(self, texture):
        self.interpreter.unregister_object(texture)

    def texture_release(self, surface):
        pass

    def get_tex_surface(self, texture, face, level, zslice, usage):
        return texture.get_surface(face, level, zslice, usage)
    
    def tex_surface_destroy(self, surface):
        self.interpreter.unregister_object(surface)

    def tex_surface_release(self, surface):
        pass

    def surface_write(self, surface, data, stride, size):
        assert surface.nblocksy * stride == size 
        surface.put_tile_raw(0, 0, surface.width, surface.height, data, stride)


class Context(Object):
    
    def __init__(self, interpreter, real):
        Object.__init__(self, interpreter, real)
        self.cbufs = []
        self.zsbuf = None

    def destroy(self):
        pass
    
    def create_blend_state(self, state):
        return state

    def bind_blend_state(self, state):
        if state is not None:
            self.real.set_blend(state)

    def delete_blend_state(self, state):
        pass
    
    def create_sampler_state(self, state):
        return state

    def delete_sampler_state(self, state):
        pass

    def bind_sampler_states(self, n, states):
        for i in range(n):
            self.real.set_sampler(i, states[i])
        
    def create_rasterizer_state(self, state):
        return state

    def bind_rasterizer_state(self, state):
        if state is not None:
            self.real.set_rasterizer(state)
        
    def delete_rasterizer_state(self, state):
        pass
    
    def create_depth_stencil_alpha_state(self, state):
        return state

    def bind_depth_stencil_alpha_state(self, state):
        if state is not None:
            self.real.set_depth_stencil_alpha(state)
            
    def delete_depth_stencil_alpha_state(self, state):
        pass

    def create_fs_state(self, state):
        tokens = str(state.tokens)
        shader = gallium.Shader(tokens)
        return shader

    create_vs_state = create_fs_state
    
    def bind_fs_state(self, state):
        self.real.set_fragment_shader(state)
        
    def bind_vs_state(self, state):
        self.real.set_vertex_shader(state)

    def delete_fs_state(self, state):
        pass
    
    delete_vs_state = delete_fs_state
    
    def set_blend_color(self, state):
        self.real.set_blend_color(state)

    def set_clip_state(self, state):
        _state = gallium.Clip()
        _state.nr = state.nr
        if state.nr:
            # FIXME
            ucp = gallium.FloatArray(gallium.PIPE_MAX_CLIP_PLANES*4)
            for i in range(len(state.ucp)):
                for j in range(len(state.ucp[i])):
                    ucp[i*4 + j] = state.ucp[i][j]
            _state.ucp = ucp
        self.real.set_clip(_state)

    def set_constant_buffer(self, shader, index, state):
        if state is not None:
            self.real.set_constant_buffer(shader, index, state.buffer)

    def set_framebuffer_state(self, state):
        _state = gallium.Framebuffer()
        _state.width = state.width
        _state.height = state.height
        _state.num_cbufs = state.num_cbufs
        for i in range(len(state.cbufs)):
            _state.set_cbuf(i, state.cbufs[i])
        _state.set_zsbuf(state.zsbuf)    
        self.real.set_framebuffer(_state)
        
        self.cbufs = state.cbufs
        self.zsbuf = state.zsbuf

    def set_polygon_stipple(self, state):
        self.real.set_polygon_stipple(state)

    def set_scissor_state(self, state):
        self.real.set_scissor(state)

    def set_viewport_state(self, state):
        self.real.set_viewport(state)

    def set_sampler_textures(self, n, textures):
        for i in range(n):
            self.real.set_sampler_texture(i, textures[i])

    def set_vertex_buffers(self, n, vbufs):
        for i in range(n):
            vbuf = vbufs[i]
            self.real.set_vertex_buffer(
                i,
                pitch = vbuf.pitch,
                max_index = vbuf.max_index,
                buffer_offset = vbuf.buffer_offset,
                buffer = vbuf.buffer,
            )

    def set_vertex_elements(self, n, elements):
        for i in range(n):
            self.real.set_vertex_element(i, elements[i])
        self.real.set_vertex_elements(n)

    def set_edgeflags(self, bitfield):
        # FIXME
        pass
    
    def draw_arrays(self, mode, start, count):
        self.real.draw_arrays(mode, start, count)
    
    def draw_elements(self, indexBuffer, indexSize, mode, start, count):
        self.real.draw_elements(indexBuffer, indexSize, mode, start, count)
        
    def draw_range_elements(self, indexBuffer, indexSize, minIndex, maxIndex, mode, start, count):
        self.real.draw_range_elements(indexBuffer, indexSize, minIndex, maxIndex, mode, start, count)
        
    def flush(self, flags):
        self.real.flush(flags)
        if flags & gallium.PIPE_FLUSH_FRAME:
            self._update()
        return None

    def clear(self, surface, value):
        self.real.surface_clear(surface, value)
        
    def _update(self):
        self.real.flush()
    
        if self.cbufs and self.cbufs[0]:
            show_image(self.cbufs[0])
    

class Interpreter(parser.TraceDumper):
    
    def __init__(self, stream):
        parser.TraceDumper.__init__(self, stream)
        self.objects = {}
        self.result = None
        self.globl = Global(self, None)

    def register_object(self, address, object):
        self.objects[address] = object
        
    def unregister_object(self, object):
        # FIXME:
        pass

    def lookup_object(self, address):
        return self.objects[address]
    
    def interpret(self, trace):
        for call in trace.calls:
            self.interpret_call(call)

    def handle_call(self, call):
        parser.TraceDumper.handle_call(self, call)
        
        args = [self.interpret_arg(arg) for name, arg in call.args] 
        
        if call.klass:
            obj = args[0]
            args = args[1:]
        else:
            obj = self.globl
            
        method = getattr(obj, call.method)
        ret = method(*args)
        
        if call.ret and isinstance(call.ret, model.Pointer):
            self.register_object(call.ret.address, ret)

    def interpret_arg(self, node):
        translator = Translator(self)
        return translator.visit(node)
    

if __name__ == '__main__':
    parser.main(Interpreter)