/******************************************************************************
 * $Id$
 *
 * Project:  libLAS - http://liblas.org - A BSD library for LAS format data.
 * Purpose:  
 * Author:   Martin Vales, martin_gnu@mundo-r.com
 *
 ******************************************************************************
 * Copyright (c) 2008, Martin Vales
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following 
 * conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright 
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in 
 *       the documentation and/or other materials provided 
 *       with the distribution.
 *     * Neither the name of the Martin Isenburg or Iowa Department 
 *       of Natural Resources nor the names of its contributors may be 
 *       used to endorse or promote products derived from this software 
 *       without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 ****************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using LASError = System.Int32;
using LASWriterH = System.IntPtr;
using LASReaderH = System.IntPtr;
using LASPointH = System.IntPtr;
using LASGuidH = System.IntPtr;
using LASVLRH = System.IntPtr;
using LASHeaderH = System.IntPtr;

namespace LibLAS
{
    /// <summary>
    /// LASPoint class
    /// </summary>
    public class LASPoint : IDisposable
    {
        /// <summary>
        /// Returns a bitfield representing the validity of various members.
        /// </summary>
        public enum DataMemberFlag
        {
            /// <summary>
            /// ReturnNumber property flag.
            /// </summary>
            eReturnNumber = 1,
            /// <summary>
            /// NumberOfReturns property flag.
            /// </summary>
            eNumberOfReturns = 2,
            /// <summary>
            /// ScanDirection property flag.
            /// </summary>
            eScanDirection = 4,
            /// <summary>
            /// FlightLineEdge property flag.
            /// </summary>
            eFlightLineEdge = 8,
            /// <summary>
            /// Classification property flag.
            /// </summary>
            eClassification = 16,
            /// <summary>
            /// ScanAngleRank property flag.
            /// </summary>
            eScanAngleRank = 32,
            /// <summary>
            /// Time property flag.
            /// </summary>
            eTime = 64
        };

        private LASPointH hPoint;

        /// <summary>
        /// A flag to denote whether or not the point owns itself and can Dispose
        /// itself upon going out of scope. There are instances where LASReader 
        /// may actually own the point as it is read from or written to files.
        /// </summary>
        private bool mustDestroy;

        /// <summary>
        /// Create a new LASPoint from the LASPointH opaque structure
        /// </summary>
        /// <param name="hLASPoint">LASPointH opaque structure</param>
        /// <param name="ownsCStructure">Indicates whether this instance is allowed 
        /// to ownsCStructure the underlying C structure</param>
        internal LASPoint(LASPointH hLASPoint, bool ownsCStructure)
        {
            hPoint = hLASPoint;
            mustDestroy = ownsCStructure;
        }

        /// <summary>
        /// Create a generic LASPoint
        /// </summary>
        public LASPoint()
        {
            hPoint = NativeMethods.LASPoint_Create();
            mustDestroy = true;
        }

        /// <summary>
        /// Compare 2 LASPoint to be equal
        /// </summary>
        /// <param name="lasPoint">LASPoint object</param>
        /// <returns>true if lasPoint is equals to the instance.</returns>
        public bool Equals(LASPoint lasPoint)
        {
            if (X == lasPoint.X && Y == lasPoint.Y && Z == lasPoint.Z)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// test is the LASPoint is Valid
        /// </summary>
        /// <returns>true is is valid</returns>
        public bool IsValid()
        {
            if (NativeMethods.LASPoint_IsValid(hPoint) == 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            // free native resources if there are any.
            if (hPoint != IntPtr.Zero && mustDestroy)
            {
                NativeMethods.LASPoint_Destroy(hPoint);
                hPoint = IntPtr.Zero;
            }
        }

        /// <summary>
        /// The object user should call this method when they finished with the object.
        /// </summary>
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~LASPoint()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        enum ClassificationType
        {
            eCreated = 0,
            eUnclassified,
            eGround,
            eLowVegetation,
            eMediumVegetation,
            eHighVegetation,
            eBuilding,
            eLowPoint,
            eModelKeyPoint,
            eWater = 9,
            // = 10 // reserved for ASPRS Definition
            // = 11 // reserved for ASPRS Definition
            eOverlapPoints = 12
            // = 13-31 // reserved for ASPRS Definition
        };

        enum ScanAngleRankRange
        {
            eScanAngleRankMin = -90,
            eScanAngleRankMax = 90
        };

        /// <summary>
        /// gets the opaque pointer to the LASPointH instance.
        /// </summary>
        /// <returns>the opaque pointer to the LASPointH instance</returns>
        internal LASPointH GetPointer()
        {
            return hPoint;
        }

        /// <summary>
        /// Copy a LASPoint object creating a new one.
        /// </summary>
        /// <returns>a new LASPoint instance copied.</returns>
        public LASPoint Copy()
        {
            LASPointH laspointhTemp = NativeMethods.LASPoint_Copy(hPoint);
            return new LASPoint(laspointhTemp, true);
        }

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public bool Validate()
        {
            int error = NativeMethods.LASPoint_Validate(hPoint);

            if (error != 0)
            {
                LASException e = new LASException("Point data members out of range.");
                e.Data.Add("DataMemberFlag", error);
                throw e;
            }

            return true;
        }

        /// <summary>
        /// X value for the point.
        /// </summary>
        /// <remarks>This value must be scaled or offset by any header values before being set.</remarks>
        public double X
        {
            get
            {
                return NativeMethods.LASPoint_GetX(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetX(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point X.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// Y value for the point.
        /// </summary>
        /// <remarks>This value must be scaled or offset by any header values before being set.</remarks>
        public double Y
        {
            get
            {
                return NativeMethods.LASPoint_GetY(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetY(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point Y.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// Z value for the point.
        /// </summary>
        /// <remarks>This value must be scaled or offset by any header values before being set.</remarks>
        /// 
        public double Z
        {
            get
            {
                return NativeMethods.LASPoint_GetZ(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetZ(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point Z.");
                    throw e;
                }
            }
        }

        /// <summary>
        ///  intensity value for the point.
        /// </summary>
        /// <remarks>This value is the pulse return magnitude, it is optional, and it is LiDAR system specific. 
        /// </remarks>
        public UInt16 Intensity
        {
            get
            {
                return NativeMethods.LASPoint_GetIntensity(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetIntensity(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point Intensity.");
                    throw e;
                }
            }
        }

        /// <summary>
        ///  scan flags for the point -- Return number, number of returns, flightline edge, scan direction, and scan angle rank.
        /// </summary>
        /// <remarks></remarks>
        public byte ScanFlags
        {
            get
            {
                return NativeMethods.LASPoint_GetScanFlags(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetScanFlags(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point ScanFlags.");
                    throw e;
                }

            }
        }

        /// <summary>
        ///  return number for the point.
        /// </summary>
        /// <remarks>The return number is "the pulse return number for a given output pulse."  The first return number starts with the value 1. valid from 1 to 6.</remarks>
        public UInt16 ReturnNumber
        {
            get
            {
                return NativeMethods.LASPoint_GetReturnNumber(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetReturnNumber(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point ReturnNumber.");
                    throw e;
                }
            }
        }

        /// <summary>
        ///  total number of returns for a given pulse.
        /// </summary>
        /// <remarks>Valid values are from 1-5. </remarks>
        public UInt16 NumberOfReturns
        {
            get
            {
                return NativeMethods.LASPoint_GetNumberOfReturns(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetNumberOfReturns(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point NumberOfReturns.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// scan direction for a given pulse.
        /// </summary>
        /// <remarks>Valid values are 0 or 1, with 1 being a positive scan direction and 0 being a negative scan direction.</remarks>
        public UInt16 ScanDirection
        {
            get
            {
                return NativeMethods.LASPoint_GetScanDirection(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetScanDirection(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point ScanDirection.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// whether or not a given pulse is an edge point.
        /// </summary>
        /// <remarks>Valid values are 0 or 1, with 1 being an edge point and 0 being interior.</remarks>
        public UInt16 FlightLineEdge
        {
            get
            {
                return NativeMethods.LASPoint_GetFlightLineEdge(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetFlightLineEdge(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point FlightLineEdge.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// classification for the point.
        /// </summary>
        public byte Classification
        {
            get
            {
                return NativeMethods.LASPoint_GetClassification(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetClassification(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point Classification.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// the scan angle for the point.
        /// </summary>
        public SByte ScanAngleRank
        {
            get
            {
                return NativeMethods.LASPoint_GetScanAngleRank(hPoint);
            }
            set
            {

                LASError error = NativeMethods.LASPoint_SetScanAngleRank(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point ScanAngleRank.");
                    throw e;
                }
            }
        }

        /// <summary>
        /// arbitrary user data for the point.
        /// </summary>
        public byte UserData
        {
            get
            {
                return NativeMethods.LASPoint_GetUserData(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetUserData(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point UserData.");
                    throw e;
                }
            }
        }

        /// <summary>
        ///  time for the point.
        /// </summary>
        public double Time
        {
            get
            {
                return NativeMethods.LASPoint_GetTime(hPoint);
            }
            set
            {
                LASError error = NativeMethods.LASPoint_SetTime(hPoint, value);
                if ((Int32)error != 0)
                {
                    LASException e = new LASException("Exception in Set Point Time.");
                    throw e;
                }
            }
        }
    }
}
