/** @file SymbolLibrary.java
*
* @author marco corvi
* @date dec 2012
*
* @brief TopoDroid drawing: area symbol library
* --------------------------------------------------------
* Copyright This sowftare is distributed under GPL-3.0 or later
* See the file COPYING.
* --------------------------------------------------------
*/
package com.topodroid.DistoX;
import java.util.Locale;
import java.util.Stack;
import java.util.List;
import java.util.ArrayList;
import java.util.TreeSet;
import java.io.File;
import java.io.PrintWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;
class SymbolLibrary
{
protected String mPrefix;
protected ArrayList< Symbol > mSymbols;
private SymbolNode mRoot;
int mSymbolNr;
SymbolLibrary( String prefix )
{
mPrefix = prefix;
mRoot = null;
mSymbols = new ArrayList< Symbol >();
}
int size() { return mSymbolNr; }
// ----------------------------------------------------
// used by DrawingDxf and DrawingSurface (for the palette)
ArrayList< Symbol > getSymbols() { return mSymbols; }
protected boolean addSymbol( Symbol v )
{
boolean ret = true;
SymbolNode n = new SymbolNode( v );
if ( mRoot == null ) {
mRoot = n;
mRoot.color = BLACK;
} else {
for ( SymbolNode n0 = mRoot; ; ) {
int c = compare( n0.value.mThName, v.mThName );
if ( c < 0 ) {
if ( n0.left == null ) {
n0.left = n;
n.parent = n0;
break;
} else {
n0 = n0.left;
}
} else if ( c > 0 ) {
if ( n0.right == null ) {
n0.right = n;
n.parent = n0;
break;
} else {
n0 = n0.right;
}
} else {
TDLog.Error( "Double insertion of symbol " + mPrefix + v.mThName );
ret = false;
break;
}
}
// rebalance
if ( ret ) insert_case1( n );
}
if ( ret ) {
mSymbols.add( v );
mSymbolNr = mSymbols.size();
}
return ret;
}
protected Symbol get( String th_name )
{
return ( mRoot == null )? null : mRoot.get( th_name );
}
// ============================================================
int getSymbolIndex( Symbol symbol )
{
for ( int k=0; k<mSymbolNr; ++k ) {
if ( symbol == mSymbols.get(k) ) return k;
}
return -1;
}
int getSymbolIndexByThName( String th_name )
{
for ( int k=0; k<mSymbolNr; ++k ) if ( mSymbols.get(k).mThName.equals( th_name) ) return k;
return -1;
}
int getSymbolIndexByFilename( String fname )
{
for ( int k=0; k<mSymbolNr; ++k ) if ( mSymbols.get(k).mThName.equals( fname) ) return k;
return -1;
}
// ===============================================
// SymbolInterface
// this is used only by PT Cmap
boolean hasSymbolByThName( String th_name ) { return ( null != get( th_name ) ); }
// this is used by loadUserXXX
protected boolean hasSymbolByFilename( String fname ) { return ( null != get( fname ) ); }
Symbol getSymbolByFilename( String fname ) { return get( fname ); }
// Symbol getSymbolByThName( String th_name ) { return get( th_name ); }
Symbol getSymbolByIndex( int k ) { return ( k < 0 || k >= mSymbolNr )? null : mSymbols.get( k ); }
String getSymbolName( int k ) { return ( k < 0 || k >= mSymbolNr )? null : mSymbols.get(k).getName(); }
String getSymbolThName( int k ) { return ( k < 0 || k >= mSymbolNr )? null : mSymbols.get(k).getThName(); }
Paint getSymbolPaint( int k ) { return ( k < 0 || k >= mSymbolNr )? null : mSymbols.get(k).getPaint(); }
Path getSymbolPath( int k ) { return ( k < 0 || k >= mSymbolNr )? null : mSymbols.get(k).getPath(); }
boolean isSymbolOrientable( int k ) { return ( k < 0 || k >= mSymbolNr )? false : mSymbols.get( k ).isOrientable(); }
boolean isSymbolEnabled( int k ) { return ( k < 0 || k >= mSymbolNr )? false : mSymbols.get( k ).isEnabled(); }
ArrayList<String> getSymbolNames()
{
ArrayList<String> ret = new ArrayList<String>();
for ( Symbol s : mSymbols ) ret.add( s.getName() );
return ret;
}
boolean isSymbolEnabled( String th_name )
{
Symbol a = get( th_name );
return ( a != null )? a.isEnabled() : false;
}
void resetOrientations() { for ( Symbol s : mSymbols ) s.setAngle(0); }
// ========================================================================
// CSURVEY attributes
int getSymbolCsxLayer( int k ) { return ( k < 0 || k >= mSymbolNr )? -1 : mSymbols.get(k).mCsxLayer; }
int getSymbolCsxType( int k ) { return ( k < 0 || k >= mSymbolNr )? -1 : mSymbols.get(k).mCsxType; }
int getSymbolCsxCategory( int k ) { return ( k < 0 || k >= mSymbolNr )? -1 : mSymbols.get(k).mCsxCategory; }
int getSymbolCsxPen( int k ) { return ( k < 0 || k >= mSymbolNr )? -1 : mSymbols.get(k).mCsxPen; }
int getSymbolCsxBrush( int k ) { return ( k < 0 || k >= mSymbolNr )? -1 : mSymbols.get(k).mCsxBrush; }
// ========================================================================
protected void sortSymbolByName( int start )
{
for ( int k=start+1; k<mSymbolNr; ) {
Symbol prev = mSymbols.get(k-1);
Symbol curr = mSymbols.get(k);
if ( prev.getName().compareTo(curr.getName()) > 0 ) { // swap
mSymbols.set( k-1, curr );
mSymbols.set( k, prev );
if ( k > start+1 ) --k;
} else {
++k;
}
}
}
// protected boolean tryLoadMissingSymbol( String prefix, String th_name, String fname )
// {
// String locale = "name-" + Locale.getDefault().toString().substring(0,2);
// String iso = "ISO-8859-1";
// // String iso = "UTF-8";
// // if ( locale.equals( "name-es" ) ) iso = "ISO-8859-1";
// Symbol symbol = getSymbolByFilename( fname );
// if ( symbol == null ) {
// File file = new File( filename );
// if ( ! file.exists() ) return false;
// symbol = new Symbol( file.getPath(), locale, iso );
// mSymbols.add( symbol );
// }
// if ( symbol == null ) return false;
// // Log.v( TopoDroidApp.TAG, "enabling missing symbol " + prefix + th_name );
// symbol.setEnabled( true ); // TopoDroidApp.mData.isSymbolEnabled( "a_" + symbol.mThName ) );
// makeEnabledList( );
// return true;
// }
// prefix: p_ l_ a_
protected void makeEnabledList( )
{
for ( Symbol symbol : mSymbols ) {
TopoDroidApp.mData.setSymbolEnabled( mPrefix + symbol.mThName, symbol.mEnabled );
if ( symbol.mEnabled ) {
}
}
}
// symbols = palette.mPaletteAreas etc. (filenames)
protected void makeEnabledListFromStrings( TreeSet<String> symbols )
{
for ( Symbol symbol : mSymbols ) symbol.setEnabled( false );
for ( String fname : symbols ) {
Symbol symbol = getSymbolByFilename( fname );
if ( symbol != null ) symbol.setEnabled( true );
}
makeEnabledList( );
}
void setRecentSymbols( Symbol recent[] )
{
int k = 0;
for ( Symbol symbol : mSymbols ) {
if ( symbol.mEnabled ) {
recent[k++] = symbol;
if ( k >= ItemDrawer.NR_RECENT ) break;
}
}
}
void writePalette( PrintWriter pw )
{
for ( Symbol symbol : mSymbols ) {
if ( symbol.isEnabled( ) ) pw.format( " %s", symbol.getFilename() );
}
}
void toDataStream( DataOutputStream dos )
{
StringBuilder sb = new StringBuilder();
for ( Symbol symbol : mSymbols ) {
if ( symbol.isEnabled( ) ) { sb.append( symbol.getFilename() ).append(","); }
}
try {
// int str_len = sb.length();
// dos.writeInt( str_len );
dos.writeUTF( sb.toString() );
} catch ( IOException e ) { }
}
// -------------------------------------------------------------
static final boolean BLACK = true;
static final boolean RED = false;
static int compare( String s1, String s2 )
{
int l1 = s1.length();
int l2 = s2.length();
int kk = ( l1 < l2 )? l1 : l2;
for ( int k=0; k < kk; ++k ) {
if ( s1.charAt(k) < s2.charAt(k) ) return -1;
if ( s1.charAt(k) > s2.charAt(k) ) return +1;
}
if ( l1 < l2 ) return -1;
if ( l1 > l2 ) return +1;
return 0;
}
private class SymbolNode
{
SymbolNode parent;
SymbolNode left;
SymbolNode right;
boolean color;
Symbol value; // the Node value is the Symbol
SymbolNode( Symbol v )
{
parent = null;
left = null;
right = null;
color = RED;
value = v;
}
// @param name query key (symbol filename)
Symbol get( String name )
{
int c = compare( value.mThName, name );
if ( c == 0 ) return value;
if ( c < 0 ) return ( left == null )? null : left.get( name );
return ( right == null )? null : right.get( name );
}
}
// -----------------------------------------------------
private void insert_case1( SymbolNode n )
{
if ( n.parent == null ) {
n.color = BLACK;
} else {
insert_case2( n );
}
}
// n.parent != null
private void insert_case2( SymbolNode n )
{
if ( n.parent.color ) return; // isBlack( n.parent )
insert_case3( n );
}
// n.parent != null && n.parent RED
private void insert_case3( SymbolNode n )
{
SymbolNode u = uncle( n );
if ( u != null && isRed( u ) ) {
n.parent.color = BLACK;
u.color = BLACK;
SymbolNode g = grandparent( n );
g.color = RED;
insert_case1( g );
} else {
insert_case4( n );
}
}
// n.parent == g.left && n = n.parent.right ( n.parent RED )
// or symmetric
private void insert_case4( SymbolNode n )
{
SymbolNode p = n.parent;
SymbolNode g = p.parent;
if ( isRight( n ) && isLeft( p ) ) { // rotate_left( n, p, g );
n.parent = g;
if ( g != null ) {
g.left = n;
} else {
mRoot = n;
}
if ( n.left != null ) n.left.parent = p;
p.right = n.left;
p.parent = n;
n.left = p;
n = n.left; // continue with n.left
} else if ( isLeft( n ) && isRight( p ) ) { // rotate_right( n, p, g );
n.parent = g;
if ( g != null ) {
g.right = n;
} else {
mRoot = n;
}
if ( n.right != null ) n.right.parent = p;
p.left = n.right;
p.parent = n;
n.right = p;
n = n.right; // continue with n.right
}
insert_case5( n );
}
// n.parent RED but n.uncle BLACK
private void insert_case5( SymbolNode n )
{
SymbolNode g = grandparent( n );
SymbolNode p = n.parent;
p.color = BLACK;
g.color = RED;
SymbolNode gp = g.parent;
if ( gp != null ) {
if ( g == gp.left ) {
gp.left = p;
} else {
gp.right = p;
}
} else {
mRoot = p;
}
p.parent = gp;
if ( isLeft( n ) ) { // rotate_right( n.parent, g, g.parent );
// assert( p == g.left );
g.left = p.right;
if ( p.right != null ) p.right.parent = g;
p.right = g;
g.parent = p;
} else { // rotate_left( n.parent, g, g.parent );
// assert( p == g.right );
g.right = p.left;
if ( p.left != null ) p.left.parent = g;
p.left = g;
g.parent = p;
}
}
private SymbolNode grandparent( SymbolNode n )
{
return ( n != null && n.parent != null )? n.parent.parent : null;
}
private SymbolNode uncle( SymbolNode n )
{
SymbolNode p = n.parent;
if ( p == null ) return null;
SymbolNode g = p.parent;
if ( g == null ) return null;
return ( p == g.left )? g.right : g.left;
}
// private boolean isBlack( SymbolNode n ) { return ( n == null ) || n.color; }
private boolean isRed( SymbolNode n ) { return ( n != null ) && (! n.color ); }
// prereq. n.parent != null
private boolean isLeft( SymbolNode n ) { return ( n == n.parent.left ); }
private boolean isRight( SymbolNode n ) { return ( n == n.parent.right ); }
// ===========================================================================
}