#!/usr/bin/env python3
"""Deploy Go1 AMP model in MuJoCo."""
import mujoco, numpy as np, os, torch, time, sys
from mujoco import viewer

# Use AMP's rsl_rl (NOT MotrixLab's)
sys.path.insert(0, '/home/8x54zj-m/amp_go2/rsl_rl')
sys.path.insert(0, '/home/8x54zj-m/amp_go2/legged_gym')
# Force reload of rsl_rl to pick up AMP version
for mod in list(sys.modules.keys()):
    if 'rsl_rl' in mod:
        del sys.modules[mod]

XML_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                       'motrix_envs/src/motrix_envs/locomotion/go1/xmls')
CKPT = '/home/8x54zj-m/amp_go2/legged_gym/logs/go1_amp/Jul05_01-15-41_flat/model_6000.pt'

import argparse
p = argparse.ArgumentParser()
p.add_argument("--terrain", default="flat", choices=["flat", "flat_stairs"])
args = p.parse_args()

scene = "scene_flat_stairs.xml" if args.terrain == "flat_stairs" else "go1_motor_actuator.xml"
os.chdir(XML_DIR)
m = mujoco.MjModel.from_xml_string(open(scene).read())
d = mujoco.MjData(m)

# Spawn on level 1 platform if using flat_stairs
spawn_x, spawn_y, spawn_z = 0.0, 0.0, 0.45
if args.terrain == "flat_stairs":
    col = np.random.randint(0, 4)
    spawn_x = -12.0 + col * 8.0
    spawn_y = -4.0  # level 1 center
    # Sample terrain height at spawn
    floor_id = mujoco.mj_name2id(m, mujoco.mjtObj.mjOBJ_GEOM, "floor")
    if floor_id >= 0 and m.geom_type[floor_id] == mujoco.mjtGeom.mjGEOM_HFIELD:
        hf = m.geom_dataid[floor_id]
        nr, nc = int(m.hfield_nrow[hf]), int(m.hfield_ncol[hf])
        sx, sy, zt, sb = m.hfield_size[hf]
        hd = m.hfield_data[m.hfield_adr[hf]:m.hfield_adr[hf]+nr*nc].reshape(nr, nc)
        gp = m.geom_pos[floor_id]
        col_idx = int(np.clip(((spawn_x-gp[0])/sx*0.5+0.5)*(nc-1), 0, nc-1))
        row_idx = int(np.clip(((spawn_y-gp[1])/sy*0.5+0.5)*(nr-1), 0, nr-1))
        spawn_z = float(gp[2] + sb + hd[row_idx, col_idx] * zt) + 0.45
    print(f"[Terrain] flat_stairs level=1 spawn=({spawn_x:.1f},{spawn_y:.1f},{spawn_z:.2f})")

ckpt = torch.load(CKPT, map_location='cpu', weights_only=False)
normalizer = ckpt['amp_normalizer']

actor = torch.nn.Sequential(
    torch.nn.Linear(45, 512), torch.nn.ELU(),
    torch.nn.Linear(512, 256), torch.nn.ELU(),
    torch.nn.Linear(256, 128), torch.nn.ELU(),
    torch.nn.Linear(128, 12)
)
sd = {k.replace('actor.',''): v for k,v in ckpt['model_state_dict'].items() if k.startswith('actor.')}
actor.load_state_dict(sd)
actor.eval()

DEFAULT = np.array([-0.1,0.8,-1.5, 0.1,0.8,-1.5, -0.1,1.0,-1.5, 0.1,1.0,-1.5], dtype=np.float32)
LIN_VEL_SCALE, ANG_VEL_SCALE = 2.0, 0.25
DOF_POS_SCALE, DOF_VEL_SCALE = 1.0, 0.05
CMD_SCALE = np.array([2.0, 2.0, 0.25], dtype=np.float32)
KP, KD, ACT_SCALE = 20.0, 0.5, 0.25

def get_gravity(q):
    qw,qx,qy,qz = q[3],q[0],q[1],q[2]
    return np.array([2*(-qz*qx+qw*qy), -2*(qz*qy+qw*qx), 1-2*(qw*qw+qz*qz)])

def compute_obs(d, last_action, cmd):
    obs = np.zeros(45, dtype=np.float32)
    obs[0:3] = d.qvel[3:6] * ANG_VEL_SCALE
    obs[3:6] = get_gravity(d.qpos[3:7])
    obs[6:9] = cmd * CMD_SCALE
    obs[9:21] = (d.qpos[7:19] - DEFAULT) * DOF_POS_SCALE
    obs[21:33] = d.qvel[6:18] * DOF_VEL_SCALE
    obs[33:45] = last_action
    return obs

d.qpos[7:19] = DEFAULT; d.qpos[0:3] = [spawn_x, spawn_y, spawn_z]; d.qvel[:] = 0
mujoco.mj_forward(m, d)

view = viewer.launch_passive(m, d)
cmd = np.array([0.8, 0.0, 0.0], dtype=np.float32)
last_action = np.zeros(12, dtype=np.float32)

print("AMP Go1 | W/S前后 Q/E左右 A/D旋转 Space停 R重置 Esc退出")

while view.is_running():
    if view.is_running():
        obs = compute_obs(d, last_action, cmd)
        obs_t = torch.from_numpy(obs).unsqueeze(0).float()
        obs_norm = obs_t.clone()
        obs_norm[:, :43] = normalizer.normalize(obs_t[:, :43])
        with torch.no_grad():
            act = actor(obs_norm)[0].numpy()
        act = np.clip(act, -18, 18)
        last_action = act.copy()
        target = DEFAULT + act * ACT_SCALE
        d.ctrl[:] = KP * (target - d.qpos[7:19]) - KD * d.qvel[6:18]
    mujoco.mj_step(m, d)
    view.sync()
    time.sleep(0.001)
