using System;
using System.IO;
using System.Text;
/*
 BASED ON python script here
 http://binarymillenium.googlecode.com/svn/trunk/processing/psynth/bin_to_csv.py
 
 via blog posts
  
 http://binarymillenium.com/2008/08/exporting-point-clouds-from-photosynth.html
 http://binarymillenium.com/2008/08/photosynth-export-process-tutorial.html
 
 and
  
 http://getsatisfaction.com/livelabs/topics/pointcloud_exporter
 
 An output file is written with a .PLY extension. It can be opened in Meshlab;
 visit http://meshlab.sourceforge.net/ to download 
  
 Any errors are mine (http://conceptdev.blogspot.com). The colors don't seem right yet.
 */
namespace PointExtractor
{
    //TODO: struct minmax { float x; float y; float z; float x1; float y1; float z1;}
    class Program
    {
        static void Main(string[] args)
        {
            var output = new StringBuilder();
            int rowCount = 0;
            var baseFilename = args[0].Replace ("_0_0", "_0_{0}");
            for (int f = 0; f < 50; f++)
            {   // loop through up to 50 bin files
                var filename = string.Format(baseFilename, f.ToString());
                if (File.Exists(filename))
                    ReadPoints(filename, ref rowCount, ref output);
                else
                    break;
            }
 
 
            // thanks to http://knele.myopenid.com/
            string header = @"ply
format ascii 1.0
comment made by Knele
element vertex "+ rowCount +@"
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 0
property list uchar int vertex_index
end_header
";
            output.Append("0 0 0 0"); // end of ply
 
            File.WriteAllText(args[0]+".ply", header + output.ToString());
        }
        
        /// <summary>
        /// def checkfloats(fbin):
        ///     for i in range(3):
        ///         if not((abs(fbin[i]) &gt; 1e-10) and (abs(fbin[i]) &lt; 1e6)): return False
        ///     return True;
        /// </summary>
        static bool checkFloats(byte[] fbin)
        {
            for (int i = 0; i < 3; i++)
            {
                float t = System.BitConverter.ToSingle(fbin, i*4);
                if (
                    ! ( (Math.Abs(t) > 1e-10)
                     && (Math.Abs(t) < 1e6) )
                ) 
                    return false;
            }
            return true;
        }
        /// <summary>
        /// fbin = array.array('f')
        /// fbin.fromfile(fraw, 3)
        /// </summary>
        static float f(byte[] b, int i)
        { 
            var f1=new byte[4]{b[i],b[i+1],b[i+2],b[i+3]};
            return BitConverter.ToSingle(f1, 0);
        }
        /// <summary>
        ///    bin = array.array('H')
        /// bin.fromfile(fraw, 1)
        /// </summary>
        static short b(byte[] b)
        {
            var f1=new byte[2] {b[0],b[1]};
            return BitConverter.ToInt16(f1, 0);
        }
 
        /// <summary>
        /// 14 bytes long. The first 3 sets of 4 bytes are the xyz position in floating point values. 
        /// in python I had to do a byteswap on those bytes (presumably from network order) to get them to be read in right with the readfile command.
        /// The last 2 bytes is the color of the point. It's only 4-bits per color channel,
        /// </summary>
        static void ReadPoints(string filename, ref int count, ref StringBuilder output)
        {
            using (BinaryReader fraw = new BinaryReader(File.Open(filename, FileMode.Open)))
            {
                int filesize = (int)fraw.BaseStream.Length;
 
                var bsize = 14;
                //HACK: i'm trimming and extra two off this calculation than the Python script does (- 2)
                var offset = filesize - (Convert.ToInt32(filesize / bsize) - 1) * bsize - 2;
 
                var bin = new byte[offset];
                bin = fraw.ReadBytes(offset);   // "use up" the initial bytes
                int pos = sizeof(byte) * offset;
 
                // get first chunk to check
                bin = fraw.ReadBytes(14);
                pos += sizeof(byte) * 14;
                while (pos < filesize)
                {
                    bin = byteswap(bin);
                    // Comment from Python
                    // # in multipart files the header is very huge, I'm not sure how to parse it
                    // # but I do know that it generates bad floats, so just screen for bad floats:
                    if (checkFloats(bin))
                    {
                        float t1 = f(bin, 0);
                        float t2 = f(bin, 4);
                        float t3 = f(bin, 8);
                        output.Append(String.Format("{0} {1} {2} ",t1,t2,t3));  // output xyz
                        short color = b(bin); // already did byteswap above
                        var red =     (color >> 11) & 0x1f;
                        var green = (color >> 5)  & 0x3f;
                        var blue = (color >> 0) & 0x1f;
                        red = red * 255 / 31;
                        green = green * 255 / 63;
                        blue = blue * 255 / 31;
                        output.Append(String.Format(" {0} {1} {2}\r\n", red, green, blue)); // output rgb
                        //output.Append(String.Format(" {0} {1} {2}\r\n"
                        //        , red.ToString("X")
                        //        , green.ToString("X")
                        //        , blue.ToString("X")
                        //        ));
                        count++;
                        //TODO: collect min/max
                        for (int j = 0; j < 3; j++)
                        {
                            //float m1 = System.BitConverter.ToSingle(fbin, j);
                        }
                       
                    }
                    // get another 14 bytes
                    bin = fraw.ReadBytes(14);
                    pos += sizeof(byte) * 14;
                }
                // TODO: output min/max
            }
        }
 
        /// <summary>
        /// fbin.byteswap()
        /// </summary>
        private static byte[] byteswap(byte[] bin)
        {
            var b = new byte[bin.Length];   // obviously expects even number of bytes
            for (int i = 0; i < bin.Length; i = i + 2)
            {
                b[i] = bin[i+1];
                b[i+1] = bin[i];
            }
            return b;
        }
    }
}