# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# $Id: objmtl.py,v 1.3 2006/03/20 19:32:12 mbaas Exp $

from cgtypes import *

# WavefrontReaderBase
class WavefrontReaderBase:
    """Wavefront OBJ/MTL reader base class.

    This is the base class for the OBJ and the MTL reader.
    """
    
    def __init__(self):
        """Constructor.
        """
        self.linenr = 0
        self.line = ""

    # read
    def read(self, f):
        """Read the content of a file.

        f is a file like object that can be used to read the content
        of the file.
        The file is read and for each keyword a handle_<keyword>() method
        is called with the data as argument (the number of arguments depends
        on the keyword). Each argument is of type str.
        A syntax error is generated if such a handler method is not available.
        The task of these handler methods is to preprocess the data, check
        for errors and invoke the final handler methods which are just called
        after the keyword.
        Before the file is read, the begin() method is called. At the end,
        the end() method is called.
        """

        self.linenr = 0
        self.begin()
        for self.line in f:
            self.linenr+=1
            line2 = self.line.strip()
            # Ignore empty lines and comments
            if line2=="" or line2[0] in ['#', '$', '!', '@']:
                continue

            a = line2.split()
            cmd = a[0]
            args = a[1:]
            handler = getattr(self, "handle_%s"%cmd, None)
            if handler!=None:
                handler(*args)
            else:
                self.handleUnknown(cmd, args)

        self.end()

    # begin
    def begin(self):
        """Begin reading a file.

        This method is called before the file is read.
        """
        pass

    # end
    def end(self):
        """End of reading.

        This method is called after the file was read.
        """
        pass

    # handleUnknown
    def handleUnknown(self, cmd, arglist):
        """Handle unknown keywords.

        cmd is the command keyword (the 1st argument in the current line)
        and arglist is the data (the remaining arguments).

        The default implementation raises a SyntaxError exception.
        """
        raise SyntaxError, "Unknown statement in line %d: %s"%(self.linenr, self.line)


# MTLReader
class MTLReader(WavefrontReaderBase):
    """Wavefront MTL reader.

    This class can be used as base class to read a MTL file.
    """
    
    def __init__(self):
        """Constructor.
        """
        WavefrontReaderBase.__init__(self)

#    def handleUnknown(self, cmd, arglist):
#        pass
#        print "Unknown:",cmd,arglist

    # parseMapArgs
    def parseMapArgs(self, args):
        """Parse the arguments from a map_xyz line into options and names.

        args must be a list of strings containing the arguments (already
        split).
        Returns a 2-tuple (options, names) where options is a dictionary
        that contains the options that were present in args (key is
        the option string). names is a list of strings that were no option.
        """
        options = {}
        names = []
        while len(args)>0:
            a = args[0]
            # Is this an option?
            if a[0]=="-":
                if a=="-s":
                    val = tuple(map(lambda x: float(x), args[1:4]))
                    options[a] = val
                    args = args[4:]
                elif a=="-t":
                    val = tuple(map(lambda x: float(x), args[1:4]))
                    options[a] = val
                    args = args[4:]
                elif a=="-o":
                    val = tuple(map(lambda x: float(x), args[1:4]))
                    options[a] = val
                    args = args[4:]
                elif a=="-mm":
                    val = tuple(map(lambda x: float(x), args[1:3]))
                    options[a] = val
                    args = args[3:]
                elif a=="-bm":
                    options[a] = float(args[1])
                    args = args[2:]
                elif a=="-type":
                    options[a] = args[1]
                    args = args[2:]
                elif a=="-clamp" or a=="-blendu" or a=="-blendv":
                    options[a] = args[1].lower()=="on"
                    args = args[2:]
                else:
                    raise SyntaxError, "Unknown map option in line %d: %s"%(self.linenr, a)
            else:  # no option
                names.append(a)
                args = args[1:]

        return options, names

    # Pre handler methods (they must be called "handle_<keyword>")

    def handle_newmtl(self, *name):
        """New material command.

        name is the name of the material (all names are concatenated).
        """
        self.newmtl(" ".join(name))

    def handle_illum(self, model):
        """Illumination model."""
        self.illum(int(model))

    def handle_Ns(self, shininess):
        """Shininess coefficient.
        """
        self.Ns(float(shininess))

    def handle_Ka(self, r, g, b):
        """Ambient color.
        """
        self.Ka(vec3(float(r), float(g), float(b)))

    def handle_Kd(self, r, g, b):
        """Diffuse color.
        """
        self.Kd(vec3(float(r), float(g), float(b)))

    def handle_Ks(self, r, g, b):
        """Specular color.
        """
        self.Ks(vec3(float(r), float(g), float(b)))

    def handle_Ke(self, r, g, b):
        """Emissive color.
        """
        self.Ke(vec3(float(r), float(g), float(b)))

    def handle_Tr(self, alpha):
        """Transparency."""
        self.Tr(float(alpha))

    def handle_d(self, alpha):
        """Dissolve factor (transparency)."""
        self.d(float(alpha))

    def handle_Tf(self, *args):
        """Transparency (as color)."""
        if len(args)==1:
            v = vec3(float(args[0]))
        else:
            r,g,b = args
            v = vec3(float(r), float(g), float(b))
        
        self.Tf(v)

    def handle_Ni(self, ref):
        """Refraction index."""
        self.Ni(float(ref))

    def handle_sharpness(self, v):
        """Sharpness value."""
        self.sharpness(float(v))

    def handle_map_Ka(self, *mapargs):
        """Ambient texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Ka(args[0], opts)

    def handle_map_Kd(self, *mapargs):
        """Diffuse texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Kd(args[0], opts)

    def handle_map_Ks(self, *mapargs):
        """Specular texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Ks(args[0], opts)

    def handle_map_Ke(self, *mapargs):
        """Emission texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Ke(args[0], opts)

    def handle_map_Ns(self, *mapargs):
        """Shininess texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Ns(args[0], opts)

    def handle_map_d(self, *mapargs):
        """Opacity texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_d(args[0], opts)

    def handle_map_Bump(self, *mapargs):
        """Bump map texture file."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.map_Bump(args[0], opts)

    def handle_refl(self, *mapargs):
        """Reflection map."""
        opts, args = self.parseMapArgs(mapargs)
        if len(args)>0:
            self.refl(args[0], opts)

    # Handler methods

    def newmtl(self, name):
        pass

    def illum(self, model):
        """Illumination model."""
        pass

    def Ns(self, shininess):
        """Shininess."""
        pass

    def Ka(self, c):
        """Ambient color.

        c is a vec3 containing the color.
        """
        pass

    def Kd(self, c):
        """Diffuse color.

        c is a vec3 containing the color.
        """
        pass

    def Ks(self, c):
        """Specular color.

        c is a vec3 containing the color.
        """
        pass

    def Ke(self, c):
        """Emissive color.

        c is a vec3 containing the color.
        """
        pass

    def Tr(self, alpha):
        """Transparency."""
        pass

    def d(self, alpha):
        """Dissolve factor (transparency)."""
        pass

    def Tf(self, c):
        """Transparency.

        c is a vec3 containing a color.
        """
        pass

    def Ni(self, ref):
        """Refraction index."""
        pass

    def sharpness(self, v):
        """Sharpness value."""
        pass

    def map_Ka(self, mapname, options):
        pass

    def map_Kd(self, mapname, options):
        pass

    def map_Ks(self, mapname, options):
        pass

    def map_Ke(self, mapname, options):
        pass

    def map_Ns(self, mapname, options):
        pass

    def map_d(self, mapname, options):
        pass

    def map_Bump(self, mapname, options):
        pass

    def refl(self, mapname, options):
        pass


# OBJReader
class OBJReader(WavefrontReaderBase):
    """Wavefront OBJ reader.

    This class can be used as base class to read an OBJ file.
    """
    
    def __init__(self):
        """Constructor.
        """
        WavefrontReaderBase.__init__(self)
        self.v_count = 0
        self.vp_count = 0
        self.vt_count = 0
        self.vn_count = 0

    # read
    def read(self, f):
        self.v_count = 0
        self.vp_count = 0
        self.vt_count = 0
        self.vn_count = 0
        WavefrontReaderBase.read(self, f)

    # Pre handler methods (they must be called "handle_<keyword>")

    def handle_mtllib(self, *files):
        """Material library command.

        files contains the filenames.
        """
        if len(files)==0:
            raise SyntaxError, "No material library given in line %d: %s"%(self.linenr, self.line)
        self.mtllib(*files)

    def handle_usemtl(self, *name):
        """Material name.

        (all names are concatenated)
        """
        self.usemtl(" ".join(name))

    def handle_g(self, *groups):
        """Group command.

        groups are the group names that the following geometry belongs to.
        """
        if len(groups)==0:
            groups = ("default",)
#            raise SyntaxError, "No group name given in line %d: %s"%(self.linenr, self.line)
        self.g(*groups)

    def handle_s(self, group_number):
        """Smoothing group command.

        group_number is a string that contains the smoothing group number
        or "off".
        """
        group_number = group_number.lower()
        if group_number=="off":
            group_number = 0
        try:
            group_number = int(group_number)
        except:
            raise SyntaxError, "Invalid smoothing group number in line %d: %s"%(self.linenr, self.line)
        self.s(group_number)

    def handle_v(self, x, y, z, w=1):
        """Vertex definition.
        """
        self.v_count += 1
        self.v(vec4(float(x), float(y), float(z), float(w)))

    def handle_vp(self, u, v=0, w=1):
        """Vertex in parameter space.
        """
        self.vp_count += 1
        self.vp(vec3(float(u), float(v), float(w)))

    def handle_vn(self, x, y, z):
        """Normal.
        """
        self.vn_count += 1
        self.vn(vec3(float(x), float(y), float(z)))

    def handle_vt(self, u, v=0, w=0):
        """Texture vertex.
        """
        self.vt_count += 1
        self.vt(vec3(float(u), float(v), float(w)))

    def handle_p(self, *verts):
        """Points."""
        
        if len(verts)==0:
            raise SyntaxError, "At least 1 vertex required in line %d: %s"%(self.linenr, self.line)

        vlist = []
        for s in verts:
            vert = int(s)
            if vert<0:
                vert = self.v_count+vert+1
            if vert==0:
                raise ValueError, "0-index in line %d: %s"%(self.linenr, self.line)
            vlist.append(vert)
            
        self.p(*vlist)     

    def handle_l(self, *verts):
        """Line."""

        if len(verts)<2:
            raise SyntaxError, "At least 2 vertices required in line %d: %s"%(self.linenr, self.line)

        vlist = []
        for s in verts:
            a = s.split("/")
            if len(a)==0 or len(a)>2:
                raise SyntaxError, "Syntax error in line %d: %s"%(self.linenr, self.line)
            vert = int(a[0])
            if vert<0:
                vert = self.v_count+vert+1
            tvert = None
            if len(a)>1:
                if a[1]!="":
                    tvert = int(a[1])
                    if tvert<0:
                        tvert = self.vt_count+tvert+1
            if vert==0 or tvert==0:
                raise ValueError, "0-index in line %d: %s"%(self.linenr, self.line)
            vlist.append((vert, tvert))
        self.l(*vlist)

    def handle_f(self, *verts):
        """Polygonal face.
        """
        if len(verts)<3:
            raise SyntaxError, "At least 3 vertices required in line %d: %s"%(self.linenr, self.line)

        vlist = []
        for s in verts:
            a = s.split("/")
            if len(a)==0 or len(a)>3:
                raise SyntaxError, "Syntax error in line %d: %s"%(self.linenr, self.line)
            vert = int(a[0])
            if vert<0:
                vert = self.v_count+vert+1
            tvert = None
            normal = None
            if len(a)>1:
                if a[1]!="":
                    tvert = int(a[1])
                    if tvert<0:
                        tvert = self.vt_count+tvert+1
                if len(a)>2 and a[2]!="":
                    normal = int(a[2])
                    if normal<0:
                        normal = self.vn_count+normal+1
            if vert==0 or tvert==0 or normal==0:
                raise ValueError, "0-index in line %d: %s"%(self.linenr, self.line)
            vlist.append((vert, tvert, normal))
        self.f(*vlist)

    def handle_o(self, name):
        """Object name.
        """
        self.o(name)

    def handle_bevel(self, on_off):
        """Bevel interpolation on/off.
        """
        self.bevel(on_off)

    def handle_c_interp(self, on_off):
        """Color interpolation on/off.
        """
        self.c_interp(on_off)

    def handle_d_interp(self, on_off):
        """Dissolve interpolation on/off.
        """
        self.d_interp(on_off)

    def handle_lod(self, level):
        """Level of Detail.
        """
        self.lod(int(level))

    def handle_shadow_obj(self, filename):
        """Shadow object.
        """
        self.shadow_obj(filename)

    def handle_trace_obj(self, filename):
        """Shadow object.
        """
        self.trace_obj(filename)

    # Handler methods

    def call(self, filename, *args):
        pass

    def csh(self, cmd):
        pass

    def mtllib(self, *files):
        """Specification of material libraries.

        files is a sequence of file names that contain material definitions.
        """
        pass

    def usemtl(self, name):
        """Material name.

        name is a string containing the name of the material to use for
        the following elements.
        """
        pass

    def g(self, *groups):
        """Grouping statement.

        groups is a sequence of group names that the following geometry
        belongs to.
        """
        pass

    def s(self, group_number):
        """Smoothing group.

        group_number is an integer containing the smoothing group number.
        Smoothing groups should be turned off if the group number is 0.
        """
        pass

    def v(self, vert):
        """Geometric vertex.

        vert is always a vec4."""
        pass

    def vp(self, vert):
        """A point in parameter space.

        This vertex is used for free-form curves or surfaces.
        vert is always a vec3.
        """
        pass

    def vn(self, normal):
        """Normal vector.
        
        normal is a vec3.
        """
        pass

    def vt(self, tvert):
        """Texture vertex.
        
        tvert is always a vec3.
        """
        pass

    def p(self, *verts):
        """Points.

        verts is a list of vertex indices. The indices are always >0
        (negative indices are automatically converted to their
        corresponding positive indices).
        All indices are 1-based. If an index in the file was 0, an exception
        was already thrown.
        """
        print "POINT",verts

    def l(self, *verts):
        """Line.

        verts contains 2-tuples (vert, tvert) which contains the indices
        to the vertexa and the texture vertex. tvert may be None.
        The indices are always >0 (negative indices are automatically
        converted to their corresponding positive indices).
        All indices are 1-based. If an index in the file was 0, an exception
        was already thrown.
        """
        pass

    def f(self, *verts):
        """Polygonal face.
        
        verts contains 3-tuples (vert, tvert, normal) which contains
        the indices to the vertex, the texture vertex and the normal.
        tvert and normal may be None, otherwise the values are always >0
        (negative indices are automatically converted to their corresponding
        positive indices).
        All indices are 1-based. If an index in the file was 0, an exception
        was already thrown.
        """
        pass

    def o(self, name):
        """Optional object name.

        name is a string containing the specified name for the elements
        following this statement.
        """
        pass

    def bevel(self, on_off):
        pass

    def c_interp(self, on_off):
        pass

    def d_interp(self, on_off):
        pass

    def lod(self, level):
        """
        level is an integer.
        """
        pass

    def shadow_obj(self, filename):
        pass

    def trace_obj(self, filename):
        pass

