// Copyright © 2016-2020 Andy Goryachev <[email protected]>
package goryachev.fx.internal;
import goryachev.common.util.GlobalSettings;
import goryachev.common.util.SB;
import goryachev.common.util.SStream;
import goryachev.fx.FX;
import goryachev.fx.FxWindow;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.MenuBar;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Shape;


/**
 * Keys and functions used to store ui settings.
 */
public class FxSchema
{
	public static final String FX_PREFIX = "FX.";
	
	public static final String SFX_COLUMNS = ".COLS";
	public static final String SFX_DIVIDERS = ".DIVS";
	public static final String SFX_SELECTION = ".SEL";
	public static final String SFX_SETTINGS = ".SETTINGS";
	
	public static final String SORT_ASCENDING = "A";
	public static final String SORT_DESCENDING = "D";
	public static final String SORT_NONE = "N";
	
	public static final String WINDOW_FULLSCREEN = "F";
	public static final String WINDOW_MAXIMIZED = "X";
	public static final String WINDOW_ICONIFIED = "I";
	public static final String WINDOW_NORMAL = "N";
	
	private static final Object PROP_LOAD_HANDLER = new Object();
	private static final Object PROP_NAME = new Object();
	
	
	public static void storeWindow(String prefix, FxWindow win)
	{
		double x = win.getNormalX();
		double y = win.getNormalY();
		double w = win.getNormalWidth();
		double h = win.getNormalHeight();
		
		SStream s = new SStream();
		s.add(x);
		s.add(y);
		s.add(w);
		s.add(h);
		
		if(win.isFullScreen())
		{
			s.add(WINDOW_FULLSCREEN);
		}
		else if(win.isMaximized())
		{
			s.add(WINDOW_MAXIMIZED);
		}
		else if(win.isIconified())
		{
			s.add(WINDOW_ICONIFIED);
		}
		else
		{
			s.add(WINDOW_NORMAL);
		}

		GlobalSettings.setStream(FX_PREFIX + prefix, s);
	}
	
	
	public static void restoreWindow(String prefix, FxWindow win)
	{
		try
		{
			String id = FX_PREFIX + prefix;
			SStream s = GlobalSettings.getStream(id);
			double x = s.nextDouble(-1);
			double y = s.nextDouble(-1);
			double w = s.nextDouble(-1);
			double h = s.nextDouble(-1);
			String state = s.nextString(WINDOW_NORMAL);
			
			if((w > 0) && (h > 0))
			{
				// unnecessary anymore
				if(FX.isValidCoordinates(x, y))
				{
					// iconified windows have (x,y) of -32000 for some reason
					// their coordinates are essentially lost (unless there is a way to get them in FX)
					win.setX(x);
					win.setY(y);
				}
				win.setWidth(w);
				win.setHeight(h);
				
				switch(state)
				{
//				case WINDOW_ICONIFIED:
//					win.setIconified(true);
//					break;
				case WINDOW_FULLSCREEN:
					win.setFullScreen(true);
					break;
				case WINDOW_MAXIMIZED:
					win.setMaximized(true);
					break;
				}
			}
		}
		catch(Exception e)
		{ }
	}
	

	private static void storeLocalSettings(String prefix, LocalSettings s)
	{
		String k = prefix + SFX_SETTINGS;
		s.saveValues(k);
	}
	
	
	private static void restoreLocalSettings(String prefix, LocalSettings s)
	{
		String k = prefix + SFX_SETTINGS;
		s.loadValues(k);
	}

	
	private static void storeSplitPane(String prefix, SplitPane sp)
	{
		SStream s = new SStream();
		s.add(sp.getDividers().size());
		s.addAll(sp.getDividerPositions());
		
		String k = prefix + SFX_DIVIDERS;
		GlobalSettings.setStream(k, s);
	}
	
	
	private static void restoreSplitPane(String prefix, SplitPane sp)
	{
		String k = prefix + SFX_DIVIDERS;
		SStream s = GlobalSettings.getStream(k);
		
		// must run later because of FX split pane inability to set divider positions exactly
		FX.later(() ->
		{
			int ct = s.nextInt();
			if(sp.getDividers().size() == ct)
			{
				for(int i=0; i<ct; i++)
				{
					double div = s.nextDouble();
					sp.setDividerPosition(i, div);
				}
			}
		});
	}
	
	
	private static void storeTableView(String prefix, TableView t)
	{
		ObservableList<TableColumn<?,?>> cs = t.getColumns();
		int sz = cs.size();
		ObservableList<TableColumn<?,?>> sorted = t.getSortOrder();
		
		// columns: count,[id,width,sortOrder(0 for none, negative for descending, positive for ascending)
		SStream s = new SStream();
		s.add(sz);
		for(int i=0; i<sz; i++)
		{
			TableColumn<?,?> c = cs.get(i);
			
			int sortOrder = sorted.indexOf(c);
			if(sortOrder < 0)
			{
				sortOrder = 0;
			}
			else
			{
				sortOrder++;
				if(c.getSortType() == TableColumn.SortType.DESCENDING)
				{
					sortOrder = -sortOrder;
				}
			}
			
			s.add(c.getId());
			s.add(c.getWidth());
			s.add(sortOrder);
		}
		// FIX separate columns and width/sort
		GlobalSettings.setStream(prefix + SFX_COLUMNS, s);
		
		// selection
		int ix = t.getSelectionModel().getSelectedIndex();
		GlobalSettings.setInt(prefix + SFX_SELECTION, ix);
	}
	
	
	private static void restoreTableView(String prefix, TableView t)
	{
		ObservableList<TableColumn<?,?>> cs = t.getColumns();
		
		// columns
		SStream s = GlobalSettings.getStream(prefix + SFX_COLUMNS);
		int sz = s.nextInt();
		if(sz == cs.size())
		{
			for(int i=0; i<sz; i++)
			{
				TableColumn<?,?> c = cs.get(i);
				
				String id = s.nextString();
				double w = s.nextDouble();
				int sortOrder = s.nextInt();
				
				// TODO
			}
		}
		
		// selection
		int ix = GlobalSettings.getInt(prefix + SFX_SELECTION, -1);
		if(ix >= 0)
		{
			// if done immediately it screws up the selection model for some reason
			// FIX does not select for some reason.
			/* TODO 
			FX.later(() ->
			{
				if(ix < t.getItems().size())
				{
					t.getSelectionModel().select(ix);
				}
			});
			*/
		}
	}
	
	
	/** returns full path for the Node, starting with the window id, or null if saving is not permitted */
	private static String getFullName(String windowPrefix, Node root, Node n)
	{
		String name = getName(n);
		if(name == null)
		{
			return null;
		}
		
		SB sb = getFullNamePrivate(windowPrefix, null, root, n);
		return sb == null ? null : sb.toString();
	}


	private static SB getFullNamePrivate(String windowPrefix, SB sb, Node root, Node n)
	{
		if(n == null)
		{
			return null;
		}
		
		String name = getName(n);
		if(name == null)
		{
			return null;
		}
		
		if(n == root)
		{
			sb = new SB();
			sb.a(FX_PREFIX);
			sb.a(windowPrefix);
		}
		else
		{
			Parent p = n.getParent();
			if(p == null)
			{
				return null;
			}
			
			sb = getFullNamePrivate(windowPrefix, sb, root, p);
			if(sb == null)
			{
				return null;
			}
			
			sb.a('.');
			sb.a(name);
		}
		return sb;
	}
	

	public static void storeNode(String windowPrefix, Node root, Node n)
	{
		// TODO skip property
		
		String name = getFullName(windowPrefix, root, n);
		if(name == null)
		{
			return;
		}
		
		LocalSettings s = LocalSettings.find(n);
		if(s != null)
		{
			storeLocalSettings(name, s);
		}
		
		if(n instanceof SplitPane)
		{
			storeSplitPane(name, (SplitPane)n);
		}
		else if(n instanceof TableView)
		{
			storeTableView(name, (TableView)n);
		}
		
		if(n instanceof Parent)
		{
			for(Node ch: ((Parent)n).getChildrenUnmodifiable())
			{
				storeNode(windowPrefix, root, ch);
			}
		}
		
		// TODO trigger runnable
	}
	
	
	public static void restoreNode(String windowPrefix, Node root, Node n)
	{
		// TODO skip property
				
		String name = getFullName(windowPrefix, root, n);
		if(name == null)
		{
			return;
		}
		
		if(n instanceof SplitPane)
		{
			restoreSplitPane(name, (SplitPane)n);
		}
		else if(n instanceof TableView)
		{
			restoreTableView(name, (TableView)n);
		}
		
		if(n instanceof Parent)
		{
			for(Node ch: ((Parent)n).getChildrenUnmodifiable())
			{
				restoreNode(windowPrefix, root, ch);
			}
		}
		
		LocalSettings s = LocalSettings.find(n);
		if(s != null)
		{
			restoreLocalSettings(name, s);
		}
		
		Runnable r = getOnSettingsLoaded(n);
		if(r != null)
		{
			r.run();
		}
	}
	
	
	public static void setName(Node n, String name)
	{
		n.getProperties().put(PROP_NAME, name);
	}
	
	
	private static String getName(Node n)
	{
		Object x = n.getProperties().get(PROP_NAME);
		if(x instanceof String)
		{
			return (String)x;
		}
		
		if(n instanceof MenuBar)
		{
			return null;
		}
		else if(n instanceof Shape)
		{
			return null;
		}
		else if(n instanceof ImageView)
		{
			return null;
		}
		
		String id = n.getId();
		if(id != null)
		{
			return id;
		}
				
		return n.getClass().getSimpleName();
	}
	
	
	public static void setOnSettingsLoaded(Node n, Runnable r)
	{
		n.getProperties().put(PROP_LOAD_HANDLER, r);
	}
	
	
	private static Runnable getOnSettingsLoaded(Node n)
	{
		Object x = n.getProperties().get(PROP_LOAD_HANDLER);
		if(x instanceof Runnable)
		{
			return (Runnable)x;
		}
		return null;
	}
}