#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2011, 2012                                              *  
#*   Jose Luis Cercos Pita <jlcercos@gmail.com>                            *  
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

import math
# FreeCAD modules
from FreeCAD import Vector, Part
import FreeCAD as App
import FreeCADGui as Gui
# Module
import Instance
from shipUtils import Math

def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30):
    """ Compute ship transversal areas.
    @param ship Ship instance.
    @param draft Ship draft.
    @param roll Ship roll angle.
    @param trim Ship trim angle.
    @param yaw Ship yaw angle. Ussually you don't want to use this 
    value.
    @param n Number of sections to perform.
    @return Transversal areas (every area value is composed by x 
    coordinate and computed area)
    """
    if n < 2:
        return []
    # We will take a duplicate of ship shape in order to place it
    shape = ship.Shape.copy()
    shape.translate(Vector(0.0,0.0,-draft))
    # Rotations composition is Roll->Trim->Yaw
    shape.rotate(Vector(0.0,0.0,0.0), Vector(1.0,0.0,0.0), roll)
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,0.0,1.0), yaw)
    # Now we need to know the x range of values
    bbox = shape.BoundBox
    xmin = bbox.XMin
    xmax = bbox.XMax
    dx   = (xmax - xmin) / (n-1.0)
    # First area is equal to zero.
    areas = [[xmin, 0.0]]
    # Since we need face entities, in order to compute sections we will
    # create boxes with front face at transversal area position, 
    # compute solid common, divide by faces, and preserve only desired
    # ones.
    App.Console.PrintMessage("Computing transversal areas...\n")
    App.Console.PrintMessage("Some Inventor representation errors can be shown, ignore it please.\n")
    for i in range(1,n-1):
        App.Console.PrintMessage("%d / %d\n" % (i, n-2))
        x    = xmin + i*dx
        area = 0.0
        # Create the box
        L = xmax - xmin
        B = bbox.YMax - bbox.YMin
        p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
        box = Part.makeBox(1.5*L + x, 3.0*B, - bbox.ZMin + 1.0, p)
        # Compute common part with ship
        for s in shape.Solids:
            # Get solids intersection
            try:
                common = box.common(s)
            except:
                continue
            if common.Volume == 0.0:
                continue
            # Recompute object adding it to the scene, when we have
            # computed desired data we can remove it.
            try:
                Part.show(common)
            except:
                continue
            # Divide by faces and compute only section placed ones
            faces  = common.Faces
            for f in faces:
                faceBounds = f.BoundBox
                # Orientation filter
                if faceBounds.XMax - faceBounds.XMin > 0.00001:
                    continue
                # Place filter
                if abs(faceBounds.XMax - x) > 0.00001:
                    continue
                # Valid face, compute area
                area = area + f.Area
            # Destroy last object generated
            App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name)
        # Append transversal area
        areas.append([x, area])
    # Last area is equal to zero
    areas.append([xmax, 0.0])
    App.Console.PrintMessage("Done!\n")
    return areas

def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0):
    """ Compute ship displacement.
    @param ship Ship instance.
    @param draft Ship draft.
    @param roll Ship roll angle.
    @param trim Ship trim angle.
    @param yaw Ship yaw angle. Ussually you don't want to use this 
    value.
    @return [disp, B, Cb], \n
    disp = Ship displacement [ton].
    B    = Bouyance center [m].
    Cb   = Block coefficient.
    @note Bouyance center will returned as FreeCAD.Vector class.
    @note Returned Bouyance center is in non modified ship coordinates
    """
    # We will take a duplicate of ship shape in order to place it
    shape = ship.Shape.copy()
    shape.translate(Vector(0.0,0.0,-draft))
    # Rotations composition is Roll->Trim->Yaw
    shape.rotate(Vector(0.0,0.0,0.0), Vector(1.0,0.0,0.0), roll)
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,0.0,1.0), yaw)
    # Now we need to know box dimensions
    bbox = shape.BoundBox
    xmin = bbox.XMin
    xmax = bbox.XMax
    # Create the box
    L = xmax - xmin
    B = bbox.YMax - bbox.YMin
    p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
    box = Part.makeBox(3.0*L, 3.0*B, -bbox.ZMin + 1.0, p)
    vol = 0.0
    cog = Vector()
    for solid in shape.Solids:
        # Compute common part with ship
        try:
            common = box.common(solid)
        except:
            continue
        # Get data
        vol = vol + common.Volume
        for s in common.Solids:
            sCoG  = s.CenterOfMass
            cog.x = cog.x + sCoG.x*s.Volume
            cog.y = cog.y + sCoG.y*s.Volume
            cog.z = cog.z + sCoG.z*s.Volume
    cog.x = cog.x / vol
    cog.y = cog.y / vol
    cog.z = cog.z / vol
    Vol = L*B*abs(bbox.ZMin)
    # Undo transformations
    B   = Vector()
    B.x = cog.x*math.cos(math.radians(-yaw)) - cog.y*math.sin(math.radians(-yaw))
    B.y = cog.x*math.sin(math.radians(-yaw)) + cog.y*math.cos(math.radians(-yaw))
    B.z = cog.z
    cog.x = B.x*math.cos(math.radians(-trim)) - B.z*math.sin(math.radians(-trim))
    cog.y = B.y
    cog.z = B.x*math.sin(math.radians(-trim)) + B.z*math.cos(math.radians(-trim))
    B.x = cog.x
    B.y = cog.y*math.cos(math.radians(-roll)) - cog.z*math.sin(math.radians(-roll))
    B.z = cog.y*math.sin(math.radians(-roll)) + cog.z*math.cos(math.radians(-roll))
    B.z = B.z + draft
    # Return data
    dens = 1.025 # [tons/m3], salt water
    return [dens*vol, B, vol/Vol]

def wettedArea(shape, draft, trim):
    """ Calculate wetted ship area.
    @param shape Ship external faces instance.
    @param draft Draft.
    @param trim Trim in degrees.
    @return Wetted ship area.
    """
    area     = 0.0
    nObjects = 0
    # We will take a duplicate of ship shape in order to place it
    shape = shape.copy()
    shape.translate(Vector(0.0,0.0,-draft))
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
    # Now we need to know the x range of values
    bbox = shape.BoundBox
    xmin = bbox.XMin
    xmax = bbox.XMax
    # Create the box
    L = xmax - xmin
    B = bbox.YMax - bbox.YMin
    p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
    box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p)
    # Compute common part with ship
    for f in shape.Faces:
        # Get solids intersection
        try:
            common = box.common(f)
        except:
            continue
        area = area + common.Area
    return area

def moment(ship, draft, trim, disp, xcb):
    """ Calculate triming 1cm ship moment.
    @param ship Selected ship instance
    @param draft Draft.
    @param trim Trim in degrees.
    @param disp Displacement at selected draft and trim.
    @param xcb Bouyance center at selected draft and trim.
    @return Moment to trim ship 1cm (ton m).
    @note Moment is positive when produce positive trim.
    """
    factor = 10.0
    angle = factor*math.degrees(math.atan2(0.01,0.5*ship.Length))
    newTrim = trim + angle
    data = displacement(ship,draft,0.0,newTrim,0.0)
    mom0 = -disp*xcb
    mom1 = -data[0]*data[1].x
    return (mom1 - mom0) / factor

def FloatingArea(ship, draft, trim):
    """ Calculate ship floating area.
    @param ship Selected ship instance
    @param draft Draft.
    @param trim Trim in degrees.
    @return Ship floating area, and floating coefficient.
    """
    area     = 0.0
    cf       = 0.0
    maxX     = 0.0
    minX     = 0.0
    maxY     = 0.0
    minY     = 0.0
    # We will take a duplicate of ship shape in order to place it
    shape = ship.Shape.copy()
    shape.translate(Vector(0.0,0.0,-draft))
    shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
    # Now we need to know the x range of values
    bbox = shape.BoundBox
    xmin = bbox.XMin
    xmax = bbox.XMax
    # Create the box
    L = xmax - xmin
    B = bbox.YMax - bbox.YMin
    p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
    box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p)
    # Compute common part with ship
    for s in shape.Solids:
        # Get solids intersection
        try:
            common = box.common(s)
        except:
            continue
        if common.Volume == 0.0:
            continue
        # Recompute object adding it to the scene, when we have
        # computed desired data we can remove it.
        try:
            Part.show(common)
        except:
            continue
        # Divide by faces and compute only section placed ones
        faces  = common.Faces
        for f in faces:
            faceBounds = f.BoundBox
            # Orientation filter
            if faceBounds.ZMax - faceBounds.ZMin > 0.00001:
                continue
            # Place filter
            if abs(faceBounds.ZMax) > 0.00001:
                continue
            # Valid face, compute area
            area = area + f.Area
            maxX = max(maxX, faceBounds.XMax)
            minX = max(minX, faceBounds.XMin)
            maxY = max(maxY, faceBounds.YMax)
            minY = max(minY, faceBounds.YMin)
        # Destroy last object generated
        App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name)
    dx = maxX - minX
    dy = maxY - minY
    if dx*dy > 0.0:
        cf = area / (dx*dy)
    return [area, cf]

def BMT(ship, draft, trim=0.0):
    """ Calculate ship Bouyance center transversal distance.
    @param ship Ship instance.
    @param draft Ship draft.
    @param trim Ship trim angle.
    @return BM Bouyance to metacenter height [m].
    """
    nRoll    = 2
    maxRoll  = 7.0
    B0       = displacement(ship,draft,0.0,trim,0.0)[1]
    BM       = 0.0
    for i in range(0,nRoll):
        roll = (maxRoll/nRoll)*(i+1)
        B1   = displacement(ship,draft,roll,trim,0.0)[1]
        #     * M
        #    / \            
        #   /   \  BM     ==|>   BM = (BB/2) / tan(alpha/2)
        #  /     \          
        # *-------*
        #     BB
        BB = [B1.x - B0.x, B1.y - B0.y]
        BB = math.sqrt(BB[0]*BB[0] + BB[1]*BB[1])
        BM = BM + 0.5*BB/math.tan(math.radians(0.5*roll)) / nRoll   # nRoll is the weight function
    return BM

def mainFrameCoeff(ship, draft):
    """ Calculate main frame coefficient.
    @param ship Selected ship instance
    @param draft Draft.
    @return Main frame coefficient
    """
    cm       = 0.0
    maxY     = 0.0
    minY     = 0.0
    # We will take a duplicate of ship shape in order to place it
    shape = ship.Shape.copy()
    shape.translate(Vector(0.0,0.0,-draft))
    x    = 0.0
    area = 0.0
    # Now we need to know the x range of values
    bbox = shape.BoundBox
    xmin = bbox.XMin
    xmax = bbox.XMax
    # Create the box
    L = xmax - xmin
    B = bbox.YMax - bbox.YMin
    p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
    box = Part.makeBox(1.5*L + x, 3.0*B, - bbox.ZMin + 1.0, p)
    # Compute common part with ship
    for s in shape.Solids:
        # Get solids intersection
        try:
            common = box.common(s)
        except:
            continue
        if common.Volume == 0.0:
            continue
        # Recompute object adding it to the scene, when we have
        # computed desired data we can remove it.
        try:
            Part.show(common)
        except:
            continue
        # Divide by faces and compute only section placed ones
        faces  = common.Faces
        for f in faces:
            faceBounds = f.BoundBox
            # Orientation filter
            if faceBounds.XMax - faceBounds.XMin > 0.00001:
                continue
            # Place filter
            if abs(faceBounds.XMax - x) > 0.00001:
                continue
            # Valid face, compute area
            area = area + f.Area
            maxY = max(maxY, faceBounds.YMax)
            minY = max(minY, faceBounds.YMin)
        # Destroy last object generated
        App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name)
    dy = maxY - minY
    if dy*draft > 0.0:
        cm = area / (dy*draft)
    return cm

class Point:
    """ Hydrostatics point, that conatins: \n
    draft Ship draft [m]. \n
    trim Ship trim [deg]. \n
    disp Ship displacement [ton]. \n
    xcb Bouyance center X coordinate [m].
    wet Wetted ship area [m2].
    mom Triming 1cm ship moment [ton m].
    farea Floating area [m2].
    KBt Transversal KB height [m].
    BMt Transversal BM height [m].
    Cb Block coefficient.
    Cf Floating coefficient.
    Cm Main frame coefficient.
    @note Moment is positive when produce positive trim.
    """
    def __init__(self, ship, faces, draft, trim):
        """ Use all hydrostatics tools to define a hydrostatics 
        point.
        @param ship Selected ship instance
        @param faces Ship external faces
        @param draft Draft.
        @param trim Trim in degrees.
        """
        # Hydrostatics computation
        dispData   = displacement(ship,draft,0.0,trim,0.0)
        if not faces:
            wet    = 0.0
        else:
            wet    = wettedArea(faces,draft,trim)
        mom        = moment(ship,draft,trim,dispData[0],dispData[1].x)
        farea      = FloatingArea(ship,draft,trim)
        bm         = BMT(ship,draft,trim)
        cm         = mainFrameCoeff(ship,draft)
        # Store final data
        self.draft = draft
        self.trim  = trim
        self.disp  = dispData[0]
        self.xcb   = dispData[1].x
        self.wet   = wet
        self.farea = farea[0]
        self.mom   = mom
        self.KBt   = dispData[1].y
        self.BMt   = bm
        self.Cb    = dispData[2]
        self.Cf    = farea[1]
        self.Cm    = cm
