module ARCHER
using ..AGFFileReader: Glass, GlassID, AGF
export S_LAL12M, S_BSL7M, S_LAL13M, S_NSL3M, S_BAL41M, S_LAM60M, S_BAL3M, L_BAL35M, S_LAH60M, S_TIM2M, S_FSL5M, L_LAM69M, K_VC79M, S_BAL42M, S_LAH53M

const S_LAL12M = Glass(GlassID(AGF, 914), 1, 2.753166, -0.01207074, 0.01625163, 0.001041721, -0.0001156154, 6.773623e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, -7.683e-7, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0047, 4.2, -1.0, 7.2, 2.0, 0, 53.0, [(0.27, 0.0, 10.0), (0.28, 0.06, 10.0), (0.29, 0.15, 10.0), (0.3, 0.29, 10.0), (0.31, 0.45, 10.0), (0.32, 0.61, 10.0), (0.334, 0.774, 10.0), (0.35, 0.89, 10.0), (0.365, 0.952, 10.0), (0.37, 0.962, 10.0), (0.38, 0.976, 10.0), (0.39, 0.984, 10.0), (0.4, 0.988, 10.0), (0.404, 0.989, 10.0), (0.42, 0.992, 10.0), (0.436, 0.994, 10.0), (0.46, 0.995, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.996, 10.0), (1.529, 0.991, 10.0), (1.97, 0.966, 10.0), (2.325, 0.809, 10.0)], 1.674061, -1.0, -1.0, 0, 55.017718, 0, 4.01, 0)
const S_BSL7M = Glass(GlassID(AGF, 915), 1, 2.262505, -0.01037921, 0.009341724, 0.0006453071, -7.463858e-5, 4.096215e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 3.095e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0024, 2.0, -1.0, 7.2, 1.5, 0, 1.0, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.08, 10.0), (0.3, 0.31, 10.0), (0.31, 0.58, 10.0), (0.32, 0.77, 10.0), (0.334, 0.909, 10.0), (0.35, 0.968, 10.0), (0.365, 0.988, 10.0), (0.37, 0.991, 10.0), (0.38, 0.991, 10.0), (0.39, 0.996, 10.0), (0.4, 0.997, 10.0), (0.404, 0.997, 10.0), (0.42, 0.996, 10.0), (0.436, 0.995, 10.0), (0.46, 0.995, 10.0), (0.5, 0.996, 10.0), (0.546, 0.998, 10.0), (0.58, 0.997, 10.0), (0.62, 0.997, 10.0), (0.66, 0.997, 10.0), (0.7, 0.998, 10.0), (1.06, 0.996, 10.0), (1.529, 0.989, 10.0), (1.97, 0.965, 10.0), (2.325, 0.862, 10.0)], 1.513232, -1.0, -1.0, 0, 63.709676, 0, 2.52, 0)
const S_LAL13M = Glass(GlassID(AGF, 916), 1, 2.800915, -0.0140473, 0.01703856, 0.001170426, -0.0001225957, 6.868454e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 7.171e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0081, 3.0, -1.0, 5.7, 1.5, 0, 52.0, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.01, 10.0), (0.3, 0.03, 10.0), (0.31, 0.08, 10.0), (0.32, 0.19, 10.0), (0.334, 0.411, 10.0), (0.35, 0.68, 10.0), (0.365, 0.844, 10.0), (0.37, 0.88, 10.0), (0.38, 0.932, 10.0), (0.39, 0.958, 10.0), (0.4, 0.972, 10.0), (0.404, 0.977, 10.0), (0.42, 0.986, 10.0), (0.436, 0.989, 10.0), (0.46, 0.993, 10.0), (0.5, 0.996, 10.0), (0.546, 0.997, 10.0), (0.58, 0.996, 10.0), (0.62, 0.995, 10.0), (0.66, 0.995, 10.0), (0.7, 0.996, 10.0), (1.06, 0.995, 10.0), (1.529, 0.99, 10.0), (1.97, 0.962, 10.0), (2.325, 0.762, 10.0)], 1.689007, -1.0, -1.0, 0, 52.782035, 0, 3.6, 0)
const S_NSL3M = Glass(GlassID(AGF, 917), 1, 2.267846, -0.008623029, 0.01087772, 0.0005394286, -5.529385e-5, 3.416585e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, -1.095e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0005, 1.0, -1.0, 9.0, 1.0, 0, 1.0, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.15, 10.0), (0.334, 0.655, 10.0), (0.35, 0.924, 10.0), (0.365, 0.978, 10.0), (0.37, 0.984, 10.0), (0.38, 0.99, 10.0), (0.39, 0.995, 10.0), (0.4, 0.997, 10.0), (0.404, 0.997, 10.0), (0.42, 0.997, 10.0), (0.436, 0.997, 10.0), (0.46, 0.997, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.999, 10.0), (0.62, 0.999, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.992, 10.0), (1.97, 0.937, 10.0), (2.325, 0.827, 10.0)], 1.516509, -1.0, -1.0, 0, 58.745047, 0, 2.48, 0)
const S_BAL41M = Glass(GlassID(AGF, 918), 1, 2.402809, -0.01041283, 0.01136728, 0.0007464327, -8.517315e-5, 4.764139e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 2.775e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0031, 3.0, -1.0, 7.5, 2.5, 0, 51.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.05, 10.0), (0.31, 0.25, 10.0), (0.32, 0.51, 10.0), (0.334, 0.782, 10.0), (0.35, 0.925, 10.0), (0.365, 0.971, 10.0), (0.37, 0.978, 10.0), (0.38, 0.985, 10.0), (0.39, 0.99, 10.0), (0.4, 0.993, 10.0), (0.404, 0.993, 10.0), (0.42, 0.994, 10.0), (0.436, 0.994, 10.0), (0.46, 0.995, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.997, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.992, 10.0), (1.97, 0.974, 10.0), (2.325, 0.861, 10.0)], 1.560983, -1.0, -1.0, 0, 60.308093, 0, 2.78, 0)
const S_LAM60M = Glass(GlassID(AGF, 919), 1, 2.958448, -0.01542036, 0.02030973, 0.001302646, -0.0001238708, 7.401598e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 8.217e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0085, 2.0, -1.0, 5.4, 2.0, 0, 52.0, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.06, 10.0), (0.31, 0.27, 10.0), (0.32, 0.53, 10.0), (0.334, 0.786, 10.0), (0.35, 0.922, 10.0), (0.365, 0.967, 10.0), (0.37, 0.975, 10.0), (0.38, 0.984, 10.0), (0.39, 0.989, 10.0), (0.4, 0.992, 10.0), (0.404, 0.992, 10.0), (0.42, 0.993, 10.0), (0.436, 0.993, 10.0), (0.46, 0.995, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.993, 10.0), (1.97, 0.98, 10.0), (2.325, 0.867, 10.0)], 1.737928, -1.0, -1.0, 0, 49.027571, 0, 4.06, 0)
const S_BAL3M = Glass(GlassID(AGF, 920), 1, 2.417473, -0.008685888, 0.01396835, 0.0006180845, -5.274288e-5, 3.679696e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, -3.881e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0005, 1.0, -1.0, 9.5, 2.0, 0, 1.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.0, 10.0), (0.334, 0.096, 10.0), (0.35, 0.63, 10.0), (0.365, 0.894, 10.0), (0.37, 0.928, 10.0), (0.38, 0.963, 10.0), (0.39, 0.979, 10.0), (0.4, 0.988, 10.0), (0.404, 0.99, 10.0), (0.42, 0.994, 10.0), (0.436, 0.994, 10.0), (0.46, 0.995, 10.0), (0.5, 0.997, 10.0), (0.546, 0.998, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.991, 10.0), (1.97, 0.951, 10.0), (2.325, 0.847, 10.0)], 1.568151, -1.0, -1.0, 0, 52.737315, 0, 2.98, 0)
const L_BAL35M = Glass(GlassID(AGF, 921), 1, 2.481845, -0.01213254, 0.01179439, 0.000804573, -9.005814e-5, 4.898641e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 6.78e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0043, 1.0, -1.0, 6.6, 2.5, 0, 4.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.06, 10.0), (0.31, 0.27, 10.0), (0.32, 0.53, 10.0), (0.334, 0.786, 10.0), (0.35, 0.922, 10.0), (0.365, 0.967, 10.0), (0.37, 0.975, 10.0), (0.38, 0.984, 10.0), (0.39, 0.989, 10.0), (0.4, 0.992, 10.0), (0.404, 0.992, 10.0), (0.42, 0.993, 10.0), (0.436, 0.993, 10.0), (0.46, 0.995, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.993, 10.0), (1.97, 0.98, 10.0), (2.325, 0.867, 10.0)], 1.58642, -1.0, -1.0, 0, 60.713872, 0, 2.82, 0)
const S_LAH60M = Glass(GlassID(AGF, 922), 1, 3.244481, -0.01528454, 0.03201574, 0.001865793, -0.000141547, 1.246734e-5, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 9.635e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0037, 1.0, -1.0, 5.6, 1.0, 0, 4.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.0, 10.0), (0.334, 0.0, 10.0), (0.35, 0.27, 10.0), (0.365, 0.643, 10.0), (0.37, 0.72, 10.0), (0.38, 0.83, 10.0), (0.39, 0.88, 10.0), (0.4, 0.924, 10.0), (0.404, 0.938, 10.0), (0.42, 0.957, 10.0), (0.436, 0.97, 10.0), (0.46, 0.98, 10.0), (0.5, 0.99, 10.0), (0.546, 0.996, 10.0), (0.58, 0.997, 10.0), (0.62, 0.997, 10.0), (0.66, 0.997, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.992, 10.0), (1.97, 0.968, 10.0), (2.325, 0.808, 10.0)], 1.828944, -1.0, -1.0, 0, 37.007308, 0, 4.43, 0)
const S_TIM2M = Glass(GlassID(AGF, 923), 1, 2.535373, -0.01040568, 0.02209869, 0.0009396727, -6.499788e-5, 9.894408e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 3.044e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, 0.0051, 1.0, -1.0, 8.1, 2.0, 0, 1.0, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.0, 10.0), (0.334, 0.0, 10.0), (0.35, 0.0, 10.0), (0.365, 0.267, 10.0), (0.37, 0.44, 10.0), (0.38, 0.73, 10.0), (0.39, 0.87, 10.0), (0.4, 0.942, 10.0), (0.404, 0.955, 10.0), (0.42, 0.978, 10.0), (0.436, 0.986, 10.0), (0.46, 0.99, 10.0), (0.5, 0.994, 10.0), (0.546, 0.997, 10.0), (0.58, 0.997, 10.0), (0.62, 0.996, 10.0), (0.66, 0.996, 10.0), (0.7, 0.997, 10.0), (1.06, 0.999, 10.0), (1.529, 0.996, 10.0), (1.97, 0.973, 10.0), (2.325, 0.917, 10.0)], 1.613318, -1.0, -1.0, 0, 36.219537, 0, 2.69, 0)
const S_FSL5M = Glass(GlassID(AGF, 924), 1, 2.181284, -0.009793636, 0.007745517, 0.0005928784, -7.340057e-5, 4.012184e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, -5.246e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, 0.0022, 2.0, -1.0, 9.0, 1.5, 0, 3.0, [(0.27, 0.0, 10.0), (0.28, 0.13, 10.0), (0.29, 0.43, 10.0), (0.3, 0.73, 10.0), (0.31, 0.89, 10.0), (0.32, 0.961, 10.0), (0.334, 0.988, 10.0), (0.35, 0.995, 10.0), (0.365, 0.997, 10.0), (0.37, 0.998, 10.0), (0.38, 0.998, 10.0), (0.39, 0.999, 10.0), (0.4, 0.999, 10.0), (0.404, 0.999, 10.0), (0.42, 0.999, 10.0), (0.436, 0.999, 10.0), (0.46, 0.999, 10.0), (0.5, 0.999, 10.0), (0.546, 0.999, 10.0), (0.58, 0.999, 10.0), (0.62, 0.999, 10.0), (0.66, 0.999, 10.0), (0.7, 0.999, 10.0), (1.06, 0.998, 10.0), (1.529, 0.989, 10.0), (1.97, 0.974, 10.0), (2.325, 0.864, 10.0)], 1.484524, -1.0, -1.0, 0, 69.75631, 0, 2.46, 0)
const L_LAM69M = Glass(GlassID(AGF, 925), 1, 2.909506, -0.01373872, 0.02464585, 0.001285182, -9.119209e-5, 8.193186e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 3.17e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0031, 3.1, -1.0, 8.6, 2.0, 0, 52.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.0, 10.0), (0.334, 0.0, 10.0), (0.35, 0.22, 10.0), (0.365, 0.612, 10.0), (0.37, 0.7, 10.0), (0.38, 0.81, 10.0), (0.39, 0.87, 10.0), (0.4, 0.912, 10.0), (0.404, 0.925, 10.0), (0.42, 0.95, 10.0), (0.436, 0.962, 10.0), (0.46, 0.974, 10.0), (0.5, 0.989, 10.0), (0.546, 0.996, 10.0), (0.58, 0.995, 10.0), (0.62, 0.994, 10.0), (0.66, 0.996, 10.0), (0.7, 0.998, 10.0), (1.06, 0.0, 10.0), (1.529, 0.0, 10.0), (1.97, 0.0, 10.0), (2.325, 0.0, 10.0)], 1.727801, -1.0, -1.0, 0, 40.364781, 0, 3.24, 0)
const K_VC79M = Glass(GlassID(AGF, 926), 1, 2.537275, -0.01000404, 0.01614788, -8.966925e-5, 4.332545e-5, -1.820481e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 1.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0076, -1.0, -1.0, 9.6, 2.0, 0, -1.0, [(0.27, 0.022, 10.0), (0.28, 0.103, 10.0), (0.29, 0.26, 10.0), (0.3, 0.378, 10.0), (0.31, 0.669, 10.0), (0.32, 0.799, 10.0), (0.334, 0.891, 10.0), (0.35, 0.964, 10.0), (0.365, 0.978, 10.0), (0.37, 0.982, 10.0), (0.38, 0.985, 10.0), (0.39, 0.985, 10.0), (0.4, 0.985, 10.0), (0.404, 0.985, 10.0), (0.42, 0.985, 10.0), (0.436, 0.988, 10.0), (0.46, 0.99, 10.0), (0.5, 0.992, 10.0), (0.546, 0.994, 10.0), (0.58, 0.994, 10.0), (0.62, 0.994, 10.0), (0.66, 0.997, 10.0), (0.7, 0.997, 10.0), (1.06, 0.998, 10.0), (1.529, 0.997, 10.0), (1.97, 0.978, 10.0), (2.325, 0.0, 10.0)], 1.606477, -1.0, -1.0, 0, 57.60776, 0, 3.09, 0)
const S_BAL42M = Glass(GlassID(AGF, 927), 1, 2.460903, -0.01005747, 0.01237562, 0.0007373934, -7.773189e-5, 4.239549e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 4.545e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.002, 1.0, -1.0, 6.6, 1.5, 0, 1.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.03, 10.0), (0.3, 0.15, 10.0), (0.31, 0.36, 10.0), (0.32, 0.58, 10.0), (0.334, 0.8, 10.0), (0.35, 0.932, 10.0), (0.365, 0.973, 10.0), (0.37, 0.979, 10.0), (0.38, 0.986, 10.0), (0.39, 0.991, 10.0), (0.4, 0.993, 10.0), (0.404, 0.994, 10.0), (0.42, 0.995, 10.0), (0.436, 0.995, 10.0), (0.46, 0.996, 10.0), (0.5, 0.998, 10.0), (0.546, 0.999, 10.0), (0.58, 0.998, 10.0), (0.62, 0.998, 10.0), (0.66, 0.998, 10.0), (0.7, 0.998, 10.0), (1.06, 0.997, 10.0), (1.529, 0.993, 10.0), (1.97, 0.978, 10.0), (2.325, 0.881, 10.0)], 1.580467, -1.0, -1.0, 0, 59.002542, 0, 3.19, 0)
const S_LAH53M = Glass(GlassID(AGF, 928), 1, 3.156971, -0.01480577, 0.02790549, 0.00158408, -0.00012293, 9.591655e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 2.325, 8.688e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, -0.0052, 2.0, -1.0, 5.9, 1.0, 0, 4.2, [(0.27, 0.0, 10.0), (0.28, 0.0, 10.0), (0.29, 0.0, 10.0), (0.3, 0.0, 10.0), (0.31, 0.0, 10.0), (0.32, 0.0, 10.0), (0.334, 0.0, 10.0), (0.35, 0.38, 10.0), (0.365, 0.741, 10.0), (0.37, 0.8, 10.0), (0.38, 0.88, 10.0), (0.39, 0.925, 10.0), (0.4, 0.947, 10.0), (0.404, 0.954, 10.0), (0.42, 0.969, 10.0), (0.436, 0.977, 10.0), (0.46, 0.985, 10.0), (0.5, 0.993, 10.0), (0.546, 0.996, 10.0), (0.58, 0.996, 10.0), (0.62, 0.996, 10.0), (0.66, 0.997, 10.0), (0.7, 0.998, 10.0), (1.06, 0.999, 10.0), (1.529, 0.996, 10.0), (1.97, 0.973, 10.0), (2.325, 0.813, 10.0)], 1.801019, -1.0, -1.0, 0, 40.721498, 0, 4.43, 0)
end # module


# -----------------------------------------------------------------------------
# Implementation of Atomistic MolecularDynamicsSimulator API
# -----------------------------------------------------------------------------

"""
    NBSimulator <: MolecularDynamicsSimulator

A wrapper around NBodySimulator to implement the Atomistic API.

**Field descriptions**
- `Δt::Real` the time between timesteps, assumed to be in atomic units
- `steps::Integer` the number of timesteps for the simulation
- `t₀::Real` the starting time of the simulation, assumed to be in atomic units;
    defaults to 0
- `thermostat::Thermostat` the thermostat for the simulation;
    many options are defined by `NBodySimulator`, but a user could also define a custom thermostat;
    defaults to the `NullThermostat`
- `simulator::OrdinaryDiffEqAlgorithm` the algorithm to be used for the ODE;
    defaults to VelocityVerlet
- `potentials::Dict{Symbol,PotentialParameters}` dictionary of potentials;
    shouldn't be manipulated directly by the user
"""
@kwdef struct NBSimulator <: MolecularDynamicsSimulator
    Δt::Real       # in TIME_UNIT
    steps::Integer
    t₀::Real = 0.0 # in TIME_UNIT
    thermostat::Thermostat = NullThermostat()
    simulator::OrdinaryDiffEqAlgorithm = VelocityVerlet()
    potentials::Dict{Symbol,PotentialParameters} = Dict{Symbol,PotentialParameters}()
end
function NBSimulator(
    Δt::Unitful.Time,
    steps::Integer;
    t₀::Unitful.Time = 0.0 * TIME_UNIT,
    thermostat::Thermostat = NullThermostat(),
    simulator::OrdinaryDiffEqAlgorithm = VelocityVerlet(),
    potentials::Dict{Symbol,PotentialParameters} = Dict{Symbol,PotentialParameters}()
)
    NBSimulator(austrip(Δt), steps, austrip(t₀), thermostat, simulator, potentials)
end

# Extract the tuple of start_time, end_time from the simulator
time_range(simulator::NBSimulator) = (simulator.t₀, simulator.t₀ + simulator.steps * simulator.Δt)

function simulate(system::AbstractSystem, simulator::NBSimulator, potential::ArbitraryPotential)
    simulator.potentials[:custom] = InteratomicPotentialParameters(potential)
    simulate(system, simulator)
end
function simulate(system::AbstractSystem, simulator::NBSimulator, potential::LennardJonesParameters)
    simulator.potentials[:lennard_jones] = potential
    simulate(system, simulator)
end
function simulate(system::AbstractSystem, simulator::NBSimulator)
    nb_system = PotentialNBodySystem{ElementMassBody}(bodies(system), simulator.potentials)
    simulation = NBodySimulation(nb_system, time_range(simulator), nbs_boundary_conditions(system), simulator.thermostat, austrip(u"k"))
    NBSResult(run_simulation(simulation, simulator.simulator, dt = simulator.Δt))
end

# -----------------------------------------------------------------------------
# Integration with InteratomicPotentials
# -----------------------------------------------------------------------------

# Internal struct that represents a potential in the format accepted by NBodySimulator
# Wraps the underlying InteratomicPotential with a cache of the forces for the current timestep
struct InteratomicPotentialParameters <: PotentialParameters
    potential::ArbitraryPotential
    timestep_cache::RefValue{Real}
    force_cache::RefValue{Vector{SVector{3,Real}}}
    InteratomicPotentialParameters(potential::ArbitraryPotential) = new(potential, Ref{Real}(), Ref{Vector{SVector{3,Real}}}())
end

function NBodySimulator.get_accelerating_function(parameters::InteratomicPotentialParameters, simulation::NBodySimulation)
    bounding_box = ab_bounding_box(simulation.boundary_conditions)
    boundary_conditions = ab_boundary_conditions(simulation.boundary_conditions)
    symbols = get_atomic_symbols(simulation.system)
    numbers = get_atomic_numbers(simulation.system)
    masses = get_masses(simulation.system)
    (dv, u, v, t, i) -> begin
        if !isassigned(parameters.timestep_cache) || t != parameters.timestep_cache[]
            positions = [u[:, j] for j ∈ 1:size(u, 2)] * LENGTH_UNIT
            system = FastSystem(bounding_box, boundary_conditions, positions, symbols, numbers, masses * MASS_UNIT)
            wrapped_system = DynamicSystem(system, t * TIME_UNIT)
            parameters.timestep_cache[] = t
            parameters.force_cache[] = force(wrapped_system, parameters.potential)
        end
        dv .+= parameters.force_cache[][i] / masses[i]
    end
end

# -----------------------------------------------------------------------------
# Unitful Support for Built-in LennardJonesParameters
# -----------------------------------------------------------------------------

function NBodySimulator.LennardJonesParameters(ϵ::Unitful.Energy, σ::Unitful.Length, R::Unitful.Length)
    LennardJonesParameters(austrip(ϵ), austrip(σ), austrip(R))
end


module HydrostaticFreeSurfaceModels

using KernelAbstractions: @index, @kernel, Event, MultiEvent
using KernelAbstractions.Extras.LoopInfo: @unroll

using Oceananigans.Utils: launch!

import Oceananigans: fields

#####
##### HydrostaticFreeSurfaceModel definition
#####

include("compute_w_from_continuity.jl")
include("explicit_free_surface.jl")
include("implicit_free_surface.jl")
include("implicit_free_surface_solver.jl")
include("implicit_free_surface_solver_kernels.jl")
include("rigid_lid.jl")
include("hydrostatic_free_surface_velocity_fields.jl")
include("hydrostatic_free_surface_tendency_fields.jl")
include("hydrostatic_free_surface_model.jl")
include("show_hydrostatic_free_surface_model.jl")
include("set_hydrostatic_free_surface_model.jl")

#####
##### Time-stepping HydrostaticFreeSurfaceModels
#####

"""
    fields(model::HydrostaticFreeSurfaceModel)

Returns a flattened `NamedTuple` of the fields in `model.velocities` and `model.tracers`.
"""
fields(model::HydrostaticFreeSurfaceModel) = hydrostatic_prognostic_fields(model.velocities, model.free_surface, model.tracers)

"""
    fields(model::HydrostaticFreeSurfaceModel)

Returns a flattened `NamedTuple` of the fields in `model.velocities` and `model.tracers`.
"""
hydrostatic_prognostic_fields(velocities, free_surface, tracers) = merge((u = velocities.u,
                                                                          v = velocities.v,
                                                                          η = free_surface.η),
                                                                          tracers)

include("barotropic_pressure_correction.jl")
include("hydrostatic_free_surface_advection.jl")
include("hydrostatic_free_surface_tendency_kernel_functions.jl")
include("calculate_hydrostatic_free_surface_tendencies.jl")
include("update_hydrostatic_free_surface_model_state.jl")
include("hydrostatic_free_surface_ab2_step.jl")
include("prescribed_hydrostatic_velocity_fields.jl")

#####
##### Some diagnostics
#####

include("vertical_vorticity_field.jl")

end # module


using Molly
using DelimitedFiles

data_dir = normpath(dirname(pathof(Molly)), "..", "data")
ff_dir = joinpath(data_dir, "force_fields")
openmm_dir = joinpath(data_dir, "openmm_6mrr")

ff = OpenMMForceField(joinpath.(ff_dir, ["ff99SBildn.xml", "tip3p_standard.xml", "his.xml"])...)

atoms, atoms_data, specific_inter_lists, general_inters, neighbor_finder, coords, box_size = setupsystem(
    joinpath(data_dir, "6mrr_equil.pdb"), ff)

n_steps = 500
velocities = SVector{3}.(eachrow(readdlm(joinpath(openmm_dir, "velocities_300K.txt"))))u"nm * ps^-1"
simulator = VelocityVerlet(dt=0.0005u"ps", coupling=AndersenThermostat(300.0u"K", 1.0u"ps"))

s = System(
    atoms=atoms,
    atoms_data=atoms_data,
    general_inters=general_inters,
    specific_inter_lists=specific_inter_lists,
    coords=coords,
    velocities=velocities,
    box_size=box_size,
    neighbor_finder=neighbor_finder,
)

parallel = false
simulate!(s, simulator, 5; parallel=parallel)
@time simulate!(s, simulator, n_steps; parallel=parallel)

parallel = true
simulate!(s, simulator, 5; parallel=parallel)
@time simulate!(s, simulator, n_steps; parallel=parallel)


struct MoveGenCommon
    friends::Bitboard
    enemies::Bitboard
    empty::Bitboard
    occ::Bitboard
    ourking::Bitboard
    unpinned::Bitboard
end

function MoveGenCommon(board::Board)
    MoveGenCommon(friendly(board), enemy(board), empty(board), occupied(board), friendly(board) & kings(board), ~pinned(board))
end

function build_pawn_advances!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    if board.turn == WHITE
        build_wpawn_advances!(movestack, board, targets, common)
    else
        build_bpawn_advances!(movestack, board, targets, common)
    end
    return
end

function build_wpawn_advances!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = (pawns(board) & common.friends) & (common.unpinned | file(common.ourking))

    # Single pawn advances
    dests = (pwns << 8) & common.empty & ~RANK_8
    for dest in dests & targets
        # add a normal move
        push!(movestack, Move(dest - 8, dest))
    end

    # Double pawn advances
    doubledests = (dests << 8) & targets & RANK_4
    for dest in doubledests
        # add a double pawn move
        push!(movestack, Move(dest - 16, dest, __DOUBLE_PAWN))
    end
    return
end

function build_bpawn_advances!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = (pawns(board) & common.friends) & (common.unpinned | file(common.ourking))

    # Single pawn advances
    dests = (pwns >> 8) & common.empty & ~RANK_1
    for dest in dests & targets
        # add a normal move
        push!(movestack, Move(dest + 8, dest))
    end

    # Double pawn advances
    doubledests = (dests >> 8) & targets & RANK_5
    for dest in doubledests
        # add a double pawn move
        push!(movestack, Move(dest + 16, dest, __DOUBLE_PAWN))
    end
    return
end

function build_free_pawn_captures!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    if board.turn == WHITE
        build_free_wpawn_captures!(movestack, board, targets, common)
    else
        build_free_bpawn_captures!(movestack, board, targets, common)
    end
    return
end

function build_free_wpawn_captures!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = pawns(board) & common.friends & common.unpinned

    # "Left" pawn captures

    dests = (pwns << 9) & targets & ~FILE_H
    for dest in (dests & ~RANK_8)
        push!(movestack, Move(dest - 9, dest))
    end
    build_promo_internal!(movestack, dests & RANK_8, 9)

    # "Right" pawn captures

    dests = (pwns << 7) & targets & ~FILE_A
    for dest in (dests & ~RANK_8)
        push!(movestack, Move(dest - 7, dest))
    end
    build_promo_internal!(movestack, dests & RANK_8, 7)
    return
end

function build_free_bpawn_captures!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = pawns(board) & common.friends & common.unpinned

    # "Left" pawn captures

    dests = (pwns >> 7) & targets & ~FILE_H
    for dest in (dests & ~RANK_1)
        push!(movestack, Move(dest + 7, dest))
    end
    build_promo_internal!(movestack, dests & RANK_1, -7)

    # "Right" pawn captures

    dests = (pwns >> 9) & targets & ~FILE_A
    for dest in (dests & ~RANK_1)
        push!(movestack, Move(dest + 9, dest))
    end
    build_promo_internal!(movestack, dests & RANK_1, -9)
    return
end

function build_enpass_moves!(movestack::MoveStack, board::Board, common::MoveGenCommon)
    ep_sqr = board.enpass
    if ep_sqr !== zero(UInt8)
        king = square(common.ourking)
        for pwn in (pawnAttacks(!board.turn, ep_sqr) & pawns(board) & common.friends)
            occ = common.occ
            occ &= ~Bitboard(pwn) & ~Bitboard(ep_sqr - 24 + (board.turn.val << 4))
            occ |= Bitboard(ep_sqr)
            if isempty(bishopMoves(king, occ) & bishoplike(board) & common.enemies) && isempty(rookMoves(king, occ) & rooklike(board) & common.enemies)
                push!(movestack, Move(pwn, ep_sqr, __ENPASS))
            end
        end
    end
    return
end

function build_pawn_advance_promos!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    if board.turn == WHITE
        build_wpawn_advance_promos!(movestack, board, targets, common)
    else
        build_bpawn_advance_promos!(movestack, board, targets, common)
    end
    return
end

function build_wpawn_advance_promos!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = pawns(board) & common.friends & (common.unpinned | file(common.ourking))
    dests = (pwns << 8) & targets & RANK_8
    build_promo_internal!(movestack, dests, 8)
    return
end

function build_bpawn_advance_promos!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = pawns(board) & common.friends & (common.unpinned | file(common.ourking))
    dests = (pwns >> 8) & targets & RANK_1
    build_promo_internal!(movestack, dests, -8)
    return
end

function build_promo_internal!(movestack::MoveStack, dests::Bitboard, shift::Integer)
    for dest in dests
        _move_uint16 = Move(dest - shift, dest).val
        push!(movestack, Move(_move_uint16 | __KNIGHT_PROMO))
        push!(movestack, Move(_move_uint16 | __BISHOP_PROMO))
        push!(movestack, Move(_move_uint16 | __ROOK_PROMO))
        push!(movestack, Move(_move_uint16 | __QUEEN_PROMO))
    end
    return
end

function build_pinned_pawn_captures_and_promos!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    pwns = pawns(board) & common.friends & pinned(board)
    king = square(common.ourking)
    for pwn in pwns
        for move_to in (pawnAttacks(board.turn, pwn) & common.enemies & targets)
            if !isempty(Bitboard(pwn) & blockers(king, move_to))
                _move_uint16 = Move(pwn, move_to).val
                if !isempty(Bitboard(move_to) & RANK_18)
                    push!(movestack, Move(_move_uint16 | __KNIGHT_PROMO))
                    push!(movestack, Move(_move_uint16 | __BISHOP_PROMO))
                    push!(movestack, Move(_move_uint16 | __ROOK_PROMO))
                    push!(movestack, Move(_move_uint16 | __QUEEN_PROMO))
                else
                    push!(movestack, Move(_move_uint16))
                end
            end
        end
    end
    return
end

function push_normal!(movestack::MoveStack, sqr_from::Integer, dests::Bitboard)
    for dest in dests
        push!(movestack, Move(sqr_from, dest))
    end
    return
end

# internal function to build knight moves
function build_knight_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    for knight in (knights(board) & common.friends & common.unpinned)
        push_normal!(movestack, knight, knightMoves(knight) & targets)
    end
    return
end

# internal functions to build bishop moves
function build_bishop_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    build_free_bishop_moves!(movestack, board, targets, common)
    if !isempty(pinned(board))
        build_pinned_bishop_moves!(movestack, board, targets, common)
    end
    return
end

function build_free_bishop_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    occ = common.occ
    for bishop in (bishoplike(board) & common.friends & common.unpinned)
        push_normal!(movestack, bishop, bishopMoves(bishop, occ) & targets)
    end
    return
end

function build_pinned_bishop_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    king = square(common.ourking)
    occ = common.occ
    for bishop in (bishoplike(board) & common.friends & pinned(board))
        block_1 = blockers(king, bishop)
        for move_to in (bishopMoves(bishop, occ) & targets)
            if !isempty(Bitboard(move_to) & block_1) || !isempty(Bitboard(bishop) & blockers(king, move_to))
                push!(movestack, Move(bishop, move_to))
            end
        end
    end
    return
end

# internal functions to build rook moves
function build_rook_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    build_free_rook_moves!(movestack, board, targets, common)
    if !isempty(pinned(board))
        build_pinned_rook_moves!(movestack, board, targets, common)
    end
    return
end

function build_free_rook_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    occ = common.occ
    for rook in (rooklike(board) & common.friends & common.unpinned)
        push_normal!(movestack, rook, rookMoves(rook, occ) & targets)
    end
    return
end

function build_pinned_rook_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    king = square(common.ourking)
    occ = common.occ
    for rook in (rooklike(board) & common.friends & pinned(board))
        block_1 = blockers(king, rook)
        for move_to in (rookMoves(rook, occ) & targets)
            if !isempty(Bitboard(move_to) & block_1) || !isempty(Bitboard(rook) & blockers(king, move_to))
                push!(movestack, Move(rook, move_to))
            end
        end
    end
    return
end

# internal function to build king moves
function build_king_moves!(movestack::MoveStack, board::Board, targets::Bitboard, common::MoveGenCommon)
    king = square(common.ourking)
    for move_to in (kingMoves(king) & targets)
        if !isattacked_through_king(board, move_to)
            push!(movestack, Move(king, move_to))
        end
    end
    return
end

function build_castling!(movestack::MoveStack, board::Board, common::MoveGenCommon)
    if cancastlekingside(board)
        build_kingside_castling!(movestack, board, common)
    end
    if cancastlequeenside(board)
        build_queenside_castling!(movestack, board, common)
    end
    return
end

function build_kingside_castling!(movestack::MoveStack, board::Board, common::MoveGenCommon)
    king = square(common.ourking)
    rook = king - 3
    if isempty(common.occ & blockers(king, rook)) && !isattacked(board, king - 1) && !isattacked(board, king - 2)
        push!(movestack, Move(king, king - 2, __KING_CASTLE))
    end
    return
end

function build_queenside_castling!(movestack::MoveStack, board::Board, common::MoveGenCommon)
    king = square(common.ourking)
    rook = king + 4
    if isempty(common.occ & blockers(king, rook)) && !isattacked(board, king + 1) && !isattacked(board, king + 2)
        push!(movestack, Move(king, king + 2, __QUEEN_CASTLE))
    end
    return
end

"""
    gen_quiet_moves!(movestack::MoveStack, board::Board)

Generate all the quiet moves on a `board`, pushing each `Move` to the `MoveStack`.
"""
function gen_quiet_moves!(movestack::MoveStack, board::Board)
    common = MoveGenCommon(board)
    if isdoublecheck(board)
        build_king_moves!(movestack, board, common.empty, common)
        return
    end
    if ischeck(board)
        if isempty(checkers(board) & knights(board))
            targets = blockers(square(common.ourking), square(checkers(board)))
        else
            # if a knight gives check, only *quiet* move is for king to step away
            build_king_moves!(movestack, board, common.empty, common)
            return
        end
    else
        build_castling!(movestack, board, common)
        targets = common.empty
    end
    build_pawn_advances!(movestack, board, targets, common)
    build_king_moves!(movestack, board, common.empty, common)
    build_knight_moves!(movestack, board, targets, common)
    build_bishop_moves!(movestack, board, targets, common)
    build_rook_moves!(movestack, board, targets, common)
    return
end

"""
    gen_noisy_moves!(movestack::MoveStack, board::Board)

Generate all the noisy moves on a `board`, pushing each `Move` to the `MoveStack`.
"""
function gen_noisy_moves!(movestack::MoveStack, board::Board)
    common = MoveGenCommon(board)
    enemies = common.enemies
    if isdoublecheck(board)
        build_king_moves!(movestack, board, enemies, common)
        return
    end
    if ischeck(board)
        targets = checkers(board)
        build_pawn_advance_promos!(movestack, board, blockers(square(common.ourking), square(targets)), common)
    else
        targets = enemies
        build_pawn_advance_promos!(movestack, board, common.empty, common)
    end
    build_free_pawn_captures!(movestack, board, targets, common)
    build_pinned_pawn_captures_and_promos!(movestack, board, targets, common)
    build_enpass_moves!(movestack, board, common)
    build_king_moves!(movestack, board, enemies, common)
    build_knight_moves!(movestack, board, targets, common)
    build_bishop_moves!(movestack, board, targets, common)
    build_rook_moves!(movestack, board, targets, common)
    return
end

"""
    gen_moves!(movestack::Movestack, board::Board)

Generate all the possible moves in the position.
"""
function gen_moves!(movestack::MoveStack, board::Board)
    gen_quiet_moves!(movestack, board)
    gen_noisy_moves!(movestack, board)
    return
end


# Launch with `julia --project --track-allocation=user`
import Pkg
Pkg.develop(path = ".")
import Profile

include("common.jl")
constructor = ENV["ALLOCATION_CONSTRUCTOR"]

thermo_state_map = Dict(
    "ρeq" => thermo_state_ρeq,
    "pθq" => thermo_state_pθq,
    "pTq" => thermo_state_pTq,
)
thermo_state = thermo_state_map[constructor]

thermo_state() # compile first
Profile.clear_malloc_data()
thermo_state()


using TextAnalysis: AveragePerceptron

@testset "Average Perceptron Tagger" begin
    tagger = PerceptronTagger(false)

    @testset "Basic" begin
        @test typeof(tagger.classes) == Set{Any}
        @test length(tagger.classes) == 0
        @test typeof(tagger.model) == AveragePerceptron

        fit!(tagger, [[("today","NN"),("is","VBZ"),("good","JJ"),("day","NN")]])
        @test length(keys(tagger.model.weights)) == 51
        @test tagger.classes == tagger.model.classes == Set(["JJ", "VBZ", "NN"])
    end

    @testset "Average Perceptron Tagger (pretrained)" begin
        tagger = PerceptronTagger(true)

        @test typeof(tagger.classes) == Set{Any}
        @test length(tagger.classes) == 75
        @test typeof(tagger.model) == AveragePerceptron
    end

    sample_file = joinpath(dirname(@__FILE__), "data", "poem.txt")

    @testset "Tagging over sentences and documents" begin
        tagger = PerceptronTagger(true)
        text = "This is a text"
        @test tagger(text) == predict(tagger, text)

        sd = StringDocument(text)
        @test length(predict(tagger, text)) == 4
        @test length(predict(tagger, sd)) == 4

        text2 = read(sample_file, String)
        fd = FileDocument(sample_file)
        @test length(predict(tagger, fd)) == length(predict(tagger, text2))

        td = TokenDocument(text)
        @test length(predict(tagger, td)) == 4
    end
end


path,_ = splitdir(@__FILE__)
using PyPlot
import JLD

#Packages I haven't developed
import Interpolations
# using PyPlot
import JLD
import CSV
import Dierckx

# Packages I played a very minor role
import PreComp
import Composites
import VTKtools

# Packages I played a minor role
import OptParams
import Atmosphere
import CCBlade #new CCBlade package w/o ND splined af data
import BeamFEA
import Snopt
import Gradients

# Packages I played a major role
import BrentMin
import MotorPower
import Akima
# import VLM
include("$path/../oldvlm.jl")
import BPM
import AirfoilPrep

printiter = 4600
#TODO verify this works with parallel

include("$path/../objective.jl")
include("$path/../propstructmisc.jl")
include("$path/../propwingmisc.jl")
include("$path/../setup.jl")
include("$path/../save_vtk.jl")

detailed_output_accel = []
detailed_output_climb = []
detailed_output_cruise = []
design_accel = []
design_climb = []
design_cruise = []
detailed_system = []

rc("figure", figsize=(60*1.8, 60))
rc("font", size=10.0)
rc("lines", linewidth=1.5)
rc("lines", markersize=3.0)
rc("legend", frameon=false)
# rc("figure", dpi=300)
rc("axes.spines", right=false, top=false)
rc("axes", linewidth=15)
# rc("figure.subplot", left=0.27, bottom=0.2, top=0.8, right=.95)
rc("figure.subplot", left=0.01, bottom=0.01, top=0.99, right=.99)
rc("axes", color_cycle=["348ABD", "A60628", "009E73", "7A68A6", "D55E00", "CC79A7"])
plot_cycle=["#348ABD", "#A60628", "#009E73", "#7A68A6", "#D55E00", "#CC79A7"]

ioff()

index = 70
failnum = 0
imagenum = 0
iterations = zeros(1)
objective1 = ones(1)*100
objectiveColor = ones(1)
indexcorrection = 0
background_transp = ones(1)
objcol = 0.0

while index<51521

    # try

close("all")

println("imagenum: $imagenum index: $index")
# success = true
# while success

if index == 2170
    index = 21070
    indexcorrection = 18900
end

filename = "$(path)/Journal_GIF/p8_cr100_db76correct_span$(index).jld"
summary = JLD.load(filename)
image = imread("$(path)/Journal_PNG/blade.$(lpad(imagenum,4,0)).png")
# success = false
# failnum = 0
# catch
#     failnum += 1
#     index += 1
#
# end

detailed_output_accel = summary["detailed_output_accel"]
detailed_output_climb = summary["detailed_output_climb"]
detailed_output_cruise = summary["detailed_output_cruise"]
design_accel = summary["design_accel"]
design_climb = summary["design_climb"]
design_cruise = summary["design_cruise"]
detailed_system = summary["detailed_system"]
obj = summary["objective"]

G = linspace(0,detailed_system.gama,100)-pi/2

xtr = detailed_system.dist_accel+detailed_system.radius*cos.(G)
ytr = detailed_system.radius*sin.(G)+detailed_system.radius

pathx = [0;detailed_system.dist_accel;xtr;detailed_system.dist_accel+detailed_system.dist_climb] # dist_climb includes detailed_system.xtr
plotx = [0,detailed_system.dist_accel,detailed_system.dist_accel+detailed_system.xtr,detailed_system.dist_accel+detailed_system.dist_climb]
pathy = [0;0;ytr; 15.24]

iterations = append!(iterations, iterations[end]+index-indexcorrection)

if pathx[end]<1.0

    objective1 = append!(objective1, objective1[end])

else

    objective1 = append!(objective1, pathx[end])

end

w_height = 0

imagenum += 1
index += 70

figname = "Journal_GIF$(lpad(imagenum,4,0))"
figure(figname)

# SETUP
axes()
axis("off")

#TOP TEXTS

# text(0.04, 0.95, "Props (#): 2 4    16 32     Noise (dBa): 60 65          Cruise (MPH): 50        150",
# verticalalignment="bottom", horizontalalignment="left",
# color=(0.0, 0.0, 0.0, 1.0), fontsize=180)
#
# text(0.212, 0.95, "8",
# verticalalignment="bottom", horizontalalignment="left",
# color=(0.0, 0.0, 0.0, 1.0), fontsize=180,
# bbox=Dict("facecolor"=>"none", "edgecolor"=>"black", "boxstyle"=>"round,pad=0.1","linewidth"=>"15"))
#
# text(0.57, 0.95, "70",
# verticalalignment="bottom", horizontalalignment="left",
# color=(0.0, 0.0, 0.0, 1.0), fontsize=180,
# bbox=Dict("facecolor"=>"none", "edgecolor"=>"black", "boxstyle"=>"round,pad=0.1","linewidth"=>"15"))
#
# text(0.843, 0.95, "100",
# verticalalignment="bottom", horizontalalignment="left",
# color=(0.0, 0.0, 0.0, 1.0), fontsize=180,
# bbox=Dict("facecolor"=>"none", "edgecolor"=>"black", "boxstyle"=>"round,pad=0.1","linewidth"=>"15"))
#

con_names = ["Weight",
"Noise",
"Range",
"Lift",
"Thrust",
"Tip Separation",
"Tip Speed",
"Stall",
"Cruise Speed",
"Composite Failure",
"Buckling",
"Battery Capacity"]

con_dic_names = [:totalmass,
:noise_accel,
:range,
:Lift_cruise,
:Drag_cruise,
:radiussize,
:machtip_accel,
:cl_max_accel,
:cruisespeed,
:p_stress_climb,
:p_buckling_climb,
:capacity_climb]

# for names in keys(detailed_system.optconvals)
#     println(names)
# end

constraint_y = collect(range(.82,-.07,length(con_names)))

objcol = 0

#CONSTRAINTS
for i = 1:length(con_names)
    try #catch if one of the constraints listed above is not in the list here
        background_transp = maximum(detailed_system.optconvals[con_dic_names[i]])*100
    catch
        background_transp = 0.0
    end

    # if background_transp< 0.0
    #     background_transp = 0.0
    # elseif background_transp > 1.0
    #     background_transp = 1.0
    # end

    if objcol < background_transp
        objcol = background_transp
    end

    coolwarm_constr_color = PyPlot.cm[:coolwarm](background_transp)

    # println(background_transp)
    #Background
    text(0.915, constraint_y[i], con_names[i],
    verticalalignment="bottom", horizontalalignment="center",
    color=Tuple(coolwarm_constr_color), fontsize=130,fontweight = 1000)
    # Actual text
    # text(0.915, constraint_y[i], con_names[i],
    # verticalalignment="bottom", horizontalalignment="center",
    # color=(0.0, 0.0, 0.0, 1.0), fontsize=140,fontweight = 540.0,)
end

objectiveColor = append!(objectiveColor, objcol)

#LABEL TEXTS
text(0.1, 0.91, "Props On Wing",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=160)

text(0.55, 0.90, "Takeoff Profile",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=160)

text(0.44, 0.468, "Optimization Progression",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=160)

text(0.915, 0.91, "Constraints",
verticalalignment="bottom", horizontalalignment="center",
color=(0.0, 0.0, 0.0, 1.0), fontsize=170)

# VTK Image
# Scaled by 1/2 when outputting in paraview
axes([-0.050, 0.04, 0.35, 0.68])
imshow(image)

text(210, 0, "Blade Design",
verticalalignment="bottom", horizontalalignment="center",
color=(0.0, 0.0, 0.0, 1.0), fontsize=160)

xoff = 40

text(285-xoff, 50, "Composite Failure",
verticalalignment="bottom", horizontalalignment="center",
color=(0.0, 0.0, 0.0, 1.0), fontsize=130)#,rotation = "vertical")

text(275-xoff, 321, "Buckling Failure",
verticalalignment="bottom", horizontalalignment="center",
color=(0.0, 0.0, 0.0, 1.0), fontsize=130)#,rotation = "vertical")

# TOP Bar Annotation
center = 178
offset = 95

text(205-xoff, center-offset, "Violated",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

text(205-xoff, center, "Constrained",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

text(205-xoff, center+offset, "Satisfied",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

# BOTTOM Bar Annotation
center = 445
offset = 95

text(205-xoff, center-offset, "Violated",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

text(205-xoff, center, "Constrained",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

text(205-xoff, center+offset, "Satisfied",
verticalalignment="bottom", horizontalalignment="left",
color=(0.0, 0.0, 0.0, 1.0), fontsize=95)

axis("off")

# Wing and Props
axes([0.0, 0.56, 0.4, 0.6])
x_tips = [0.0,1/8,2/8,3/8,4/8,5/8,6/8,7/8,1.0]
x = ((x_tips[2:end]-x_tips[1:end-1])/2+x_tips[1:end-1])*11.4
y = ones(length(x))*w_height
areas = ones(length(x))*pi*design_accel.p_Rtip^2*85000
# areas = ones(length(x))*pi*.7125^2*85000 #tip to tip for tuning scaling factor
scatter(x,y,s=areas,alpha=0.5)
plot([0.1,11.3],ones(2)*w_height,linewidth=75.0)
axis("off")

# Takeoff
axes([0.47, 0.655, .34, 0.238])
plot(pathx[1:2],pathy[1:2],"-",linewidth=25.0,color = "#1a455e")
plot(pathx[2:102],pathy[2:102],"-",linewidth=25.0,color = "#28698f")
plot(pathx[102:103],pathy[102:103],"-",linewidth=25.0,color = "#348ABD")
tick_params(which="major",length=10*7,width=2*7,labelsize=16*7)
xlabel("Distance (m)",fontsize=140)
xticks(plotx, rotation = 35)
ylabel("Height (m)",fontsize=140)
yticks([0,15.24],["0","15.24\n (50 ft)"],rotation = 90)
ylim([0.0,20.0])
xlim([0.0,100.0])
axis("equal")

coolwarm_obj_color = PyPlot.cm[:coolwarm](objectiveColor)

# Objective
axes([0.38, 0.119, 0.40, 0.35])
for i = 1:length(iterations)-1
    semilogy(iterations[i:i+1]/1000/35, objective1[i:i+1],"-",c = Tuple(coolwarm_obj_color[i,:]),linewidth=25.0)
end
xlabel("Iterations (1k)",fontsize=140)
ylabel("Takeoff Distance (m)",fontsize=140)
yticks(rotation = 30)
# ylim([0,100])
# xlim([0,100])
tick_params(which="major",length=10*7,width=2*7,labelsize=16*7)
tick_params(which="minor",length=10*7,width=2*7,labelsize=16*7)

# legend(title = "Cruise Constraint (m/s)",loc="upper center", ncol = length(cr_factor), bbox_to_anchor=(0.5, 1.30))#loc="center left", bbox_to_anchor=(1, 0.5))
# legend(title = "Cruise Constraint (m/s)",loc="upper center", ncol = length(cr_factor), bbox_to_anchor=(0.5, -.20))
savefig("$path/Journal_GIF/Output/$figname.png")#,transparent = false)

    # catch
    #     index += 70
    #     failnum+=1
    #     println("$failnum")
    # end
end

# run(`cd Journal_GIF/Output ; pwd ; convert -delay 2 -loop 0 *.pdf Moore2018_3.gif`)


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/FEMQuad.jl/blob/master/LICENSE

using Test

@testset "FEMQuad.jl" begin
    @testset "test_glquad" begin include("test_glquad.jl") end
    @testset "test_gltri" begin include("test_gltri.jl") end
    @testset "test_gltet" begin include("test_gltet.jl") end
    @testset "test_glwed" begin include("test_glwed.jl") end
    @testset "test_glpyr" begin include("test_glpyr.jl") end
end


using Sockets
using SHA
username = "revox" # Replace this with your username

socket = Sockets.connect("51.15.127.80", 2811)
println("Connected to Duino-Coin server")

server_ver = String(read(socket, 3))
println("Server is on version: ", server_ver)

while true
	write(socket, string("JOB,", String(username), ",MEDIUM"))
	job = String(read(socket, 87))
	#println("Job received: ", job)

	job = split(job, ",")
	lastBlockHash = job[1]
	result = job[2]
	difficulty = parse(Int32, job[3]) * 100

	for i = 0:difficulty
		stringToHash = string(lastBlockHash, string.(i))
		ducos1 = bytes2hex(sha1(stringToHash))

		if ducos1 == result
			write(socket, string(i, ",,Julia Miner"))
			feedback = String(read(socket, 4))
			if feedback == "GOOD"
				println("Accepted share ", i, "\tDifficulty ", difficulty)
				break
			else
				println("Rejected share ", i, "\tDifficulty ", difficulty)
				break
			end
		end
		i += 1
	end
end


"""
    $(TYPEDEF)

Supertype for all Lyceum environments.
"""
abstract type AbstractEnvironment end

"""
    $(TYPEDSIGNATURES)

Return a subtype of `Shapes.AbstractShape` describing the state space of `env`.

See also: [`getstate!`](@ref), [`getstate`](@ref), [`setstate!`](@ref).
"""
@mustimplement statespace(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Store the current state of `env` in `s`.

See also: [`statespace`](@ref), [`getstate`](@ref), [`setstate!`](@ref).
"""
@mustimplement getstate!(s, env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Get the current state of `env`.

See also: [`statespace`](@ref), [`getstate!`](@ref), [`setstate!`](@ref).

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement [`statespace`](@ref) and
    [`getstate!`](@ref), which are used internally by `getstate`.
"""
@propagate_inbounds function getstate(env::AbstractEnvironment)
    s = allocate(statespace(env))
    getstate!(s, env)
    return s
end

"""
    $(TYPEDSIGNATURES)

Set the current state of `env` to `s`.

See also: [`statespace`](@ref), [`getstate!`](@ref), [`getstate`](@ref).

!!! note
    Implementers of `AbstractEnvironment` subtypes must guarantee that calls to
    other "getter" functions (e.g. `getreward`) after a call to `setstate!` reflect the
    new state `s`.
"""
@mustimplement setstate!(env::AbstractEnvironment, s)

"""
    $(TYPEDSIGNATURES)

Return a subtype of `Shapes.AbstractShape` describing the observation space of `env`.

See also: [`getobservation!`](@ref), [`getobservation`](@ref).
"""
@mustimplement observationspace(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Store the current observation of `env` in `o`.

See also: [`observationspace`](@ref), [`getobservation`](@ref).
"""
@mustimplement getobservation!(o, env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Get the current observation of `env`.

See also: [`observationspace`](@ref), [`getobservation!`](@ref).

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement [`observationspace`](@ref) and
    [`getobservation!`](@ref), which are used internally by `getobservation`.
"""
@propagate_inbounds function getobservation(env::AbstractEnvironment)
    o = allocate(observationspace(env))
    getobservation!(o, env)
    o
end

"""
    $(TYPEDSIGNATURES)

Returns a subtype of `Shapes.AbstractShape` describing the action space of `env`.

See also: [`getaction!`](@ref), [`getaction`](@ref), [`setaction!`](@ref).
"""
@mustimplement actionspace(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Store the current action of `env` in `a`.

See also: [`actionspace`](@ref), [`getaction`](@ref), [`setaction!`](@ref).
"""
@mustimplement getaction!(a, env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Get the current action of `env`.

See also: [`actionspace`](@ref), [`getaction!`](@ref), [`setaction!`](@ref).

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement [`actionspace`](@ref) and
    [`getaction!`](@ref), which are used internally by `getaction`.
"""
@propagate_inbounds function getaction(env::AbstractEnvironment)
    a = allocate(actionspace(env))
    getaction!(a, env)
    a
end

"""
    $(TYPEDSIGNATURES)

Set the current action of `env` to `a`.

See also: [`actionspace`](@ref), [`getaction!`](@ref), [`getaction`](@ref).

!!! note
    Implementers of custom `AbstractEnvironment` subtypes must guarantee that calls to
    other "getter" functions (e.g. `getreward`) after a call to `setaction!` reflect the
    new action `a`.
"""
@mustimplement setaction!(env::AbstractEnvironment, a)

"""
    $(TYPEDSIGNATURES)

Returns a subtype of `Shapes.AbstractShape` describing the reward space of `env`.
Defaults to `Shapes.ScalarShape(Float64)`.

See also: [`getreward`](@ref).

!!! note
    Currently, only scalar spaces are supported.
"""
@inline rewardspace(env::AbstractEnvironment) = ScalarShape(Float64)

"""
    $(TYPEDSIGNATURES)

Get the current reward of `env` as a function of state `s`, action `a`, and observation `o`.

See also: [`rewardspace`](@ref).

!!! note
    Currently, only scalar rewards are supported, so there is no in-place `getreward!`.

!!! note
    Implementers of `AbstractEnvironment` subtypes should be careful to ensure that the result
    of `getreward` is purely a function of `s`/`a`/`o` and and not any internal, dynamic state
    contained in `env`.
"""
@mustimplement getreward(s, a, o, env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Get the current reward of `env`.

See also: [`rewardspace`](@ref).

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement `getreward(s, a, o, env)`.
"""
@propagate_inbounds function getreward(env::AbstractEnvironment)
    getreward(getstate(env), getaction(env), getobservation(env), env)
end

"""
    $(TYPEDSIGNATURES)

Reset `env` to a fixed, initial state with passive dynamics (i.e. a "zero" action).
"""
@mustimplement reset!(env::AbstractEnvironment)

"""
    randreset!([rng::Random.AbstractRNG, ], env::AbstractEnvironment)

Reset `env` to a random state with passive dynamics (i.e. a "zero" action).

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement randreset!(rng, env).
"""
@mustimplement randreset!(rng::Random.AbstractRNG, env::AbstractEnvironment)
@propagate_inbounds randreset!(env::AbstractEnvironment) = randreset!(Random.default_rng(), env)

"""
    $(TYPEDSIGNATURES)

Advance `env` forward by one timestep.

See also: [`timestep`](@ref).
"""
@mustimplement step!(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Returns `true` if state `s` and observation `o` meet an early termination condition for `env`.
Defaults to `false`.

!!! note
    Implementers of `AbstractEnvironment` subtypes should be careful to ensure that the result of
    `isdone` is purely a function of `s` and `o` and not any internal, dynamic state contained
    in `env`.
"""
isdone(s, o, env::AbstractEnvironment) = false

"""
    $(TYPEDSIGNATURES)

Returns `true` if `env` has met an early termination condition.

Internally calls `isdone(getstate(env), getobservation(env), env)`.

!!! note
    Implementers of `AbstractEnvironment` subtypes should implement `isdone(s, o, env)`.
"""
@propagate_inbounds function isdone(env::AbstractEnvironment)
    isdone(getstate(env), getobservation(env), env)
end

"""
    $(TYPEDSIGNATURES)

Returns the current simulation time, in seconds, of `env`.

See also: [`timestep`](@ref).
"""
@mustimplement Base.time(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Return the internal simulation timestep, in seconds, of `env`.

See also: [`Base.time`](@ref).
"""
@mustimplement timestep(env::AbstractEnvironment)

"""
    $(TYPEDSIGNATURES)

Return a `NamedTuple` of containing all of `env`'s spaces.

# Examples
```julia
env = FooEnv()
sp = spaces(env)
@assert statespace(env) == sp.statespace
```
"""
function spaces(env::AbstractEnvironment)
    (
        statespace = statespace(env),
        observationspace = observationspace(env),
        actionspace = actionspace(env),
        rewardspace = rewardspace(env),
    )
end


"""
	earthtide(cmd0::String=""; kwargs...)

Compute grids or time-series of solid Earth tides.

Full option list at [`earthtide`]($(GMTdoc)earthtide.html)

```julia
	G = earthtide();
	imshow(G)
```
"""
function earthtide(cmd0::String=""; kwargs...)

	(cmd0 != "") && return monolitic("earthtide", cmd0)

	d = init_module(false, kwargs...)[1]		# Also checks if the user wants ONLY the HELP mode

	cmd = "earthtide " * parse_common_opts(d, "", [:R :I :V_params :r])[1]
	cmd = parse_opt_range(d, cmd, "T")[1]
	if ((opt_S = add_opt(d, "", 'S', [:S :sun_moon])) != "")
		return finish_PS_module(d, cmd * opt_S, "", true, false, false)
	elseif ((opt_L = add_opt(d, "", 'L', [:L :location])) != "")
		return finish_PS_module(d, cmd * opt_L, "", true, false, false)
	end

	cmd = ((opt_C = add_opt(d, "", 'C', [:C :components])) != "") ? cmd * opt_C : cmd * " -Cz"
	opt_G = add_opt(d, "", 'G', [:G :grid :outgrid])
	(length(opt_G) > 3) && (cmd *= opt_G)		# G=true will give " -G", which we'll ignore  (Have to)

	return (dbg_print_cmd(d, cmd) !== nothing) ? cmd : gmt(cmd)
end


function test_python(iterations::Int64)
    iteration = 0
    total = float(0.0)
    array_length = 1000
    array::Vector{Int64} = [i for i = 0:array_length-1]
    println("iterations$(iterations)")
    while iteration < iterations
        innerloop = 0
        while innerloop < 100
            total += array[(iteration+innerloop)%array_length+1]
            innerloop += 1
        end
        iteration += 1
    end
    if total == 15150
        println("OK")
    end
    empty!(array)
end

if abspath(PROGRAM_FILE) == @__FILE__
    test_python(3)
end


function FieldGHIJjsPolynomials(arg0::RealFieldElement, arg1::RealFieldElement, arg2::RealFieldElement, arg3::RealFieldElement)
    return FieldGHIJjsPolynomials((RealFieldElement, RealFieldElement, RealFieldElement, RealFieldElement), arg0, arg1, arg2, arg3)
end

function get_gjs(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getGjs", RealFieldElement, (jint, jint), arg0, arg1)
end

function get_hjs(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getHjs", RealFieldElement, (jint, jint), arg0, arg1)
end

function get_ijs(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getIjs", RealFieldElement, (jint, jint), arg0, arg1)
end

function get_jjs(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getJjs", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_gjsd_alpha(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdAlpha", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_gjsd_beta(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdBeta", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_gjsdh(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdh", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_gjsdk(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdk", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_hjsd_alpha(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdAlpha", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_hjsd_beta(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdBeta", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_hjsdh(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdh", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_hjsdk(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdk", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_ijsd_alpha(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdAlpha", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_ijsd_beta(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdBeta", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_ijsdh(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdh", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_ijsdk(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdk", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_jjsd_alpha(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdAlpha", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_jjsd_beta(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdBeta", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_jjsdh(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdh", RealFieldElement, (jint, jint), arg0, arg1)
end

function getd_jjsdk(obj::FieldGHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdk", RealFieldElement, (jint, jint), arg0, arg1)
end


function AdamsFieldIntegrator(arg0::Field, arg1::JString, arg2::jint, arg3::jint, arg4::jdouble, arg5::jdouble, arg6::Vector{jdouble}, arg7::Vector{jdouble})
    return AdamsFieldIntegrator((Field, JString, jint, jint, jdouble, jdouble, Vector{jdouble}, Vector{jdouble}), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function AdamsFieldIntegrator(arg0::Field, arg1::JString, arg2::jint, arg3::jint, arg4::jdouble, arg5::jdouble, arg6::jdouble, arg7::jdouble)
    return AdamsFieldIntegrator((Field, JString, jint, jint, jdouble, jdouble, jdouble, jdouble), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function integrate(obj::AdamsFieldIntegrator, arg0::FieldExpandableODE, arg1::FieldODEState, arg2::RealFieldElement)
    return jcall(obj, "integrate", FieldODEStateAndDerivative, (FieldExpandableODE, FieldODEState, RealFieldElement), arg0, arg1, arg2)
end


#this will hold all attributes relating to portfolio construction
struct PortfolioConstruction{T}
  Fval::Symbol #the field that holds the values
  Fnval::Symbol #this field will hold a count in the final portfolio
  Fquantile::Symbol #the field that holds the quantiles
  Fgroup::Symbol #the field that holds the group values
  Fweight::Symbol#the field that holds the weights

  valtype::Type
  breakpoints::T #holds a tuple of breakpints
  groupweights::T #holds a tuple of weighting coefficents (after market cap normalization)

  Ngroups::Int #total number of groups
end

#main constructor
#most items are pulled from constants
function PortfolioConstruction(::Type{T}, Fval::Symbol;
  factorbreakpoints::Dict = FACTOR_BREAKPOINTS,
  factorgroupweights::Dict = FACTOR_GROUP_WEIGHTS,
  factorvaluetypes::Dict = FACTOR_VALUE_TYPES) where T

  Fnval = Symbol("N_", Fval)
  Fquantile = Symbol(:Q, Fval)
  Fgroup = Symbol(:G, Fval)
  Fweight = Symbol(:W, Fval)

  valtype::Type = factorvaluetypes[Fval]
  breakpoints::T = factorbreakpoints[Fval]
  groupweights::T = factorgroupweights[Fval]

  return PortfolioConstruction{T}(Fval, Fnval, Fquantile, Fgroup,
    Fweight, valtype, breakpoints, groupweights, length(breakpoints))
end

#constructor which does not require a type
function PortfolioConstruction(Fval::Symbol;
  factorngroups::Dict = FACTOR_N_GROUPS)

  T::Type = NTuple{factorngroups[Fval], Float64}

  return PortfolioConstruction(T, Fval)
end

#define eltype for portfolio construction
Base.eltype(::Type{PortfolioConstruction{T where T}}) = T
Base.eltype(pc::PortfolioConstruction) = eltype(typeof(pc))

#########################
# Portfolio Construction
##########################

#create a container structure for all of the portfolio constructions
struct PortfolioConstructions
  pcs::Vector{PortfolioConstruction}
  idx::Dict{Symbol, PortfolioConstruction}
  N::Int
end

#main constructor
function PortfolioConstructions(factorfields::Vector{Symbol} = FACTOR_FIELDS)
  pcs::Vector{PortfolioConstruction} = (PortfolioConstruction).(factorfields)
  idx::Dict = Dict(pc.Fval=>pc for pc ∈ pcs)

  return PortfolioConstructions(pcs, idx, length(pcs))
end

#make indexing work
Base.getindex(pcs::PortfolioConstructions, syms::Symbol...) = (s::Symbol->
  pcs.idx[s]).(syms)
Base.getindex(pcs::PortfolioConstructions, sym::Symbol) = pcs.idx[sym]
Base.getindex(pcs::PortfolioConstructions, indices::Int...) = pcs.pcs[indices]
Base.getindex(pcs::PortfolioConstructions, indice::Int) = pcs.pcs[indice]

#make length work
Base.length(pcs::PortfolioConstructions)::Int = length(pcs.pcs)

#make iteration work
function Base.iterate(pcs::PortfolioConstructions, state::Int = 1)
  (state > pcs.N) && return nothing
  return (pcs[state], state + 1)
end

Fvals(pcs::PortfolioConstructions)::Vector{Symbol} = (
  pc::PortfolioConstruction->pc.Fval).(pcs)
Fquantiles(pcs::PortfolioConstructions)::Vector{Symbol} = (
  pc::PortfolioConstruction->pc.Fquantile).(pcs)
Fgroups(pcs::PortfolioConstructions)::Vector{Symbol} = (
  pc::PortfolioConstruction->pc.Fgroup).(pcs)
Fweights(pcs::PortfolioConstructions)::Vector{Symbol} = (
  pc::PortfolioConstruction->pc.Fweight).(pcs)

function computesorts(pcs::PortfolioConstructions)
  Nsorts::Int = prod((pc->pc.Ngroups).(pcs))

  sorts::Vector{NTuple{pcs.N, Int}} = Vector{NTuple{pcs.N, Int}}(undef, Nsorts)
  sortlabels::Vector{Vector{Int}} = (pc->collect(1:pc.Ngroups)).(pcs)
  println(sortlabels)

  ctr::Int = 0
  for p ∈ Iterators.product(sortlabels...)
    ctr+=1
    sorts[ctr] = NTuple{pcs.N, Int}(p)
  end

  return sorts
end