/*
 
* Copyright (c) 2016, Metron, Inc.
 
* 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 Metron, Inc. 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 METRON, INC. 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.
 
*/
package com.metsci.glimpse.dnc.convert;

import static com.google.common.base.Charsets.US_ASCII;
import static com.metsci.glimpse.dnc.convert.Flat.FlatAttrType.FLAT_DOUBLE_ATTR;
import static com.metsci.glimpse.dnc.convert.Flat.FlatAttrType.FLAT_INT_ATTR;
import static com.metsci.glimpse.dnc.convert.Flat.FlatAttrType.FLAT_PACKED_STRING_ATTR;
import static com.metsci.glimpse.dnc.convert.Flat.FlatAttrType.FLAT_STRING_ATTR;
import static com.metsci.glimpse.dnc.convert.Flat.FlatFeatureType.FLAT_AREA_FEATURE;
import static com.metsci.glimpse.dnc.convert.Flat.FlatFeatureType.FLAT_LINE_FEATURE;
import static com.metsci.glimpse.dnc.convert.Flat.FlatFeatureType.FLAT_POINT_FEATURE;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.memmapReadOnly;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.poslim;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.readIdsMapFile;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.unpackLongIntoBytes;
import static java.lang.Double.longBitsToDouble;
import static java.lang.Integer.parseInt;
import static java.util.Arrays.sort;
import static javax.xml.bind.DatatypeConverter.printHexBinary;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.io.Files;
import com.metsci.glimpse.util.geo.LatLonGeo;

import it.unimi.dsi.fastutil.ints.Int2ObjectMap;

public class Flat
{

    
public static final Pattern flatDirnamePattern = Pattern.compile( "^dncflat([0-9][0-9])$" );


    
public static final String flatCharsetFilename = "charset";
    
public static final String flatChecksumFilename = "checksum";


    
public static final String flatLibraryNamesFilename = "library-names";
    
public static final String flatCoverageNamesFilename = "coverage-names";
    
public static final String flatFcodeNamesFilename = "fcode-names";
    
public static final String flatAttrNamesFilename = "attr-names";


    
public static final String flatChunksFilename = "chunks";
    
public static final String flatLibrariesFilename = "libraries";
    
public static final String flatFeaturesFilename = "features";
    
public static final String flatRingsFilename = "rings";
    
public static final String flatVerticesFilename = "vertices";
    
public static final String flatAttrsFilename = "attrs";
    
public static final String flatStringsFilename = "strings";


    
public static final int intsPerFlatChunk = 4;
    
public static final int doublesPerFlatLibrary = 4;
    
public static final int intsPerFlatFeature = 6;
    
public static final int intsPerFlatRing = 2;
    
public static final int doublesPerFlatVertex = 2;
    
public static final int longsPerFlatAttr = 2;


    
public static File[] flatChildDirs( File parentDir )
    
{
        
File[] childDirs = parentDir.listFiles( new FileFilter( )
        
{
            
public boolean accept( File f )
            
{
                
return ( f.isDirectory( ) && flatDirnamePattern.matcher( f.getName( ) ).matches( ) );
            
}
        
} );

        
sort( childDirs, new Comparator<File>( )
        
{
            
public int compare( File a, File b )
            
{
                
return a.getName( ).compareTo( b.getName( ) );
            
}
        
} );

        
return childDirs;
    
}


    
public static int flatDatabaseNum( File flatDir )
    
{
        
Matcher m = flatDirnamePattern.matcher( flatDir.getName( ) );
        
if ( m.matches( ) )
        
{
            
return parseInt( m.group( 1 ) );
        
}
        
else
        
{
            
throw new RuntimeException( "No flat database number found: " + flatDir.getName( ) );
        
}
    
}


    
public static Charset readFlatCharset( File flatDir ) throws IOException
    
{
        
File charsetFile = new File( flatDir, flatCharsetFilename );
        
return Charset.forName( Files.toString( charsetFile, US_ASCII ).trim( ) );
    
}


    
public static void writeFlatCharset( File flatDir, Charset charset ) throws IOException
    
{
        
File charsetFile = new File( flatDir, flatCharsetFilename );
        
Files.write( charset.name( ), charsetFile, US_ASCII );
    
}


    
public static String readFlatChecksum( File flatDir ) throws IOException
    
{
        
File checksumFile = new File( flatDir, flatChecksumFilename );
        
return Files.toString( checksumFile, US_ASCII ).trim( );
    
}


    
public static void writeFlatChecksum( File flatDir, byte[] digest ) throws IOException
    
{
        
File checksumFile = new File( flatDir, flatChecksumFilename );
        
Files.write( printHexBinary( digest ).toLowerCase( ), checksumFile, US_ASCII );
    
}


    
public static class FlatFeatureType
    
{
        
public static final byte FLAT_POINT_FEATURE = ( byte ) 0;
        
public static final byte FLAT_LINE_FEATURE = ( byte ) 1;
        
public static final byte FLAT_AREA_FEATURE = ( byte ) 2;
    
}


    
public static String flatFeatureDelineation( int featureTypeId )
    
{
        
switch ( featureTypeId )
        
{
            
case FLAT_AREA_FEATURE: return "Area";
            
case FLAT_LINE_FEATURE: return "Line";
            
case FLAT_POINT_FEATURE: return "Point";
            
default: throw new RuntimeException( "Unrecognized feature-type ID: " + featureTypeId );
        
}
    
}


    
public static class FlatAttrType
    
{
        
public static final byte FLAT_INT_ATTR = ( byte ) 0;
        
public static final byte FLAT_DOUBLE_ATTR = ( byte ) 1;
        
public static final byte FLAT_STRING_ATTR = ( byte ) 2;
        
public static final byte FLAT_PACKED_STRING_ATTR = ( byte ) 3;
    
}


    
public static Map<String,Object> readFlatAttrs( LongBuffer attrsBuf, int attrFirst, int attrCount, Int2ObjectMap<String> attrNames, ByteBuffer stringsBuf, Charset charset )
    
{
        
Map<String,Object> featureAttrsMap = new HashMap<>( );

        
poslim( attrsBuf, attrFirst, attrCount, longsPerFlatAttr );
        
while ( attrsBuf.hasRemaining( ) )
        
{
            
long attrNameIdAndType = attrsBuf.get( );
            
long attrValueParam = attrsBuf.get( );

            
int attrNameId = ( int ) ( ( attrNameIdAndType >> 32 ) & 0xFFFFFFFF );
            
String attrName = attrNames.get( attrNameId );

            
Object attrValue;
            
byte attrType = ( byte ) ( attrNameIdAndType & 0xFF );
            
switch ( attrType )
            
{
                
case FLAT_INT_ATTR:
                
{
                    
attrValue = ( int ) attrValueParam;
                
}
                
break;

                
case FLAT_DOUBLE_ATTR:
                
{
                    
attrValue = longBitsToDouble( attrValueParam );
                
}
                
break;

                
case FLAT_PACKED_STRING_ATTR:
                
{
                    
attrValue = new String( unpackLongIntoBytes( attrValueParam ), charset );
                
}
                
break;

                
case FLAT_STRING_ATTR:
                
{
                    
int stringsByteFirst = ( int ) ( ( attrValueParam >> 32 ) & 0xFFFFFFFF );
                    
int stringsByteCount = ( int ) ( attrValueParam & 0xFFFFFFFF );
                    
poslim( stringsBuf, stringsByteFirst, stringsByteCount, 1 );
                    
byte[] stringBytes = new byte[ stringsByteCount ];
                    
stringsBuf.get( stringBytes );
                    
attrValue = new String( stringBytes, charset );
                
}
                
break;

                
default:
                
{
                    
throw new RuntimeException( "Unrecognized attr-type code: " + attrType );
                
}
            
}
            
featureAttrsMap.put( attrName, attrValue );
        
}

        
return featureAttrsMap;
    
}


    
public static List<List<LatLonGeo>> readFlatAreaRings( IntBuffer ringsBuf, int ringFirst, int ringCount, DoubleBuffer verticesBuf )
    
{
        
List<List<LatLonGeo>> rings = new ArrayList<>( );
        
poslim( ringsBuf, ringFirst, ringCount, intsPerFlatRing );
        
for ( int r = 0; r < ringCount; r++ )
        
{
            
int vertexFirst = ringsBuf.get( );
            
int vertexCount = ringsBuf.get( );
            
rings.add( readFlatLineVertices( verticesBuf, vertexFirst, vertexCount ) );
        
}
        
return rings;
    
}


    
public static List<LatLonGeo> readFlatLineVertices( DoubleBuffer verticesBuf, int vertexFirst, int vertexCount )
    
{
        
List<LatLonGeo> vertices = new ArrayList<>( );
        
poslim( verticesBuf, vertexFirst, vertexCount, doublesPerFlatVertex );
        
for ( int v = 0; v < vertexCount; v++ )
        
{
            
double lat_DEG = verticesBuf.get( );
            
double lon_DEG = verticesBuf.get( );
            
vertices.add( LatLonGeo.fromDeg( lat_DEG, lon_DEG ) );
        
}
        
return vertices;
    
}


    
public static LatLonGeo readFlatPointVertex( DoubleBuffer verticesBuf, int vertexIndex )
    
{
        
poslim( verticesBuf, vertexIndex, 1, doublesPerFlatVertex );
        
double lat_DEG = verticesBuf.get( );
        
double lon_DEG = verticesBuf.get( );
        
return LatLonGeo.fromDeg( lat_DEG, lon_DEG );
    
}


    
public static Int2ObjectMap<String> readFlatFcodeNames( File flatDir, Charset charset ) throws IOException
    
{
        
return readIdsMapFile( new File( flatDir, flatFcodeNamesFilename ), charset );
    
}


    
public static Int2ObjectMap<String> readFlatAttrNames( File flatDir, Charset charset ) throws IOException
    
{
        
return readIdsMapFile( new File( flatDir, flatAttrNamesFilename ), charset );
    
}


    
public static Int2ObjectMap<String> readFlatLibraryNames( File flatDir, Charset charset ) throws IOException
    
{
        
return readIdsMapFile( new File( flatDir, flatLibraryNamesFilename ), charset );
    
}


    
public static Int2ObjectMap<String> readFlatCoverageNames( File flatDir, Charset charset ) throws IOException
    
{
        
return readIdsMapFile( new File( flatDir, flatCoverageNamesFilename ), charset );
    
}


    
public static DoubleBuffer memmapFlatLibrariesBuf( File flatDir ) throws IOException
    
{
        
return memmapReadOnly( new File( flatDir, flatLibrariesFilename ) ).asDoubleBuffer( );
    
}


    
public static IntBuffer memmapFlatRingsBuf( File flatDir ) throws IOException
    
{
        
return memmapReadOnly( new File( flatDir, flatRingsFilename ) ).asIntBuffer( );
    
}


    
public static DoubleBuffer memmapFlatVerticesBuf( File flatDir ) throws IOException
    
{
        
return memmapReadOnly( new File( flatDir, flatVerticesFilename ) ).asDoubleBuffer( );
    
}


    
public static LongBuffer memmapFlatAttrsBuf( File flatDir ) throws IOException
    
{
        
return memmapReadOnly( new File( flatDir, flatAttrsFilename ) ).asLongBuffer( );
    
}


    
public static ByteBuffer memmapFlatStringsBuf( File flatDir ) throws IOException
    
{
        
return memmapReadOnly( new File( flatDir, flatStringsFilename ) );
    
}


    
public static class FlatChunkKey
    
{
        
public final int libraryNum;
        
public final int coverageNum;

        
public FlatChunkKey( int libraryNum, int coverageNum )
        
{
            
this.libraryNum = libraryNum;
            
this.coverageNum = coverageNum;
        
}

        
@Override
        
public int hashCode( )
        
{
            
final int prime = 257;
            
int result = 1;
            
result = prime * result + libraryNum;
            
result = prime * result + coverageNum;
            
return result;
        
}

        
@Override
        
public boolean equals( Object o )
        
{
            
if ( o == this ) return true;
            
if ( o == null ) return false;
            
if ( o.getClass( ) != getClass( ) ) return false;

            
FlatChunkKey k = ( FlatChunkKey ) o;
            
return ( k.libraryNum == libraryNum && k.coverageNum == coverageNum );
        
}
    
}


    
public static Map<FlatChunkKey,IntBuffer> readFlatChunks( File flatDir ) throws IOException
    
{
        
IntBuffer chunksBuf = memmapReadOnly( new File( flatDir, flatChunksFilename ) ).asIntBuffer( );
        
IntBuffer featuresBuf = memmapReadOnly( new File( flatDir, flatFeaturesFilename ) ).asIntBuffer( );

        
Map<FlatChunkKey,IntBuffer> featuresBufs = new LinkedHashMap<>( );
        
while ( chunksBuf.hasRemaining( ) )
        
{
            
int libraryNum = chunksBuf.get( );
            
int coverageNum = chunksBuf.get( );
            
int featureFirst = chunksBuf.get( );
            
int featureCount = chunksBuf.get( );

            
FlatChunkKey chunkKey = new FlatChunkKey( libraryNum, coverageNum );

            
poslim( featuresBuf, featureFirst, featureCount, intsPerFlatFeature );
            
featuresBufs.put( chunkKey, featuresBuf.slice( ) );
        
}
        
return featuresBufs;
    
}

}