#! /usr/bin/env python
# coding: latin-1

import sys
import re
import math
import random
import time
from PyQt4 import QtCore, QtGui
from math import *
import DibuCinematica

# Clase grafica
class CinematicaQt(QtGui.QWidget):
  # Constructor
  def __init__(self):
    super(CinematicaQt, self).__init__()
    # Constantes
    self.minYi = 0.0
    self.maxYi = 50.0
    self.minVel = 0.0
    self.maxVel = 12.0
    self.g = -9.8
    #
    gArriba = QtGui.QGridLayout()
    bChk = QtGui.QPushButton("Comprobar")
    bChk.clicked.connect( self.comprobar )
    bVerSolu = QtGui.QPushButton("Corregir")
    bVerSolu.clicked.connect( self.corregir )
    self.bSimula = QtGui.QPushButton("Arranca simulacion")
    self.bSimula.clicked.connect( self.simula )
    bSig = QtGui.QPushButton("Siguiente")
    bSig.clicked.connect( self.siguiente )
    gArriba.addWidget(bChk, 0, 0)
    gArriba.addWidget(bVerSolu, 0, 1)
    gArriba.addWidget(self.bSimula, 0, 2)
    gArriba.addWidget(bSig, 0, 3)
    #
    self.vNomMag = [ 'x', 'y', 't', 'vx', 'vy' ]
    self.mEntrada = {}
    gProblema = QtGui.QHBoxLayout()
    gVariable = {}
    mTipo = { 'i': 'INICIO', 'm': 'MAX ALTURA', 's': 'SUELO' }
    for tipo in mTipo:
      gProblemaTmp = QtGui.QFormLayout()
      gProblemaTmp.addRow( QtGui.QLabel(mTipo[tipo], alignment=QtCore.Qt.AlignHCenter) )
      for nomMag in self.vNomMag:
        nomVar = nomMag + tipo
        lTmp = QtGui.QLabel("%s = " % nomMag)
        iTmp = QtGui.QLineEdit()
        self.mEntrada[nomVar] = iTmp
        gProblemaTmp.addRow(lTmp, iTmp)
      gVariable[tipo] = gProblemaTmp
    gProblema.addItem(gVariable['i'])
    gProblema.addItem(gVariable['m'])
    gProblema.addItem(gVariable['s'])
    #
    gDibuLibreta = QtGui.QHBoxLayout()
    self.dibu = DibuCinematica.DibuCinematica()
    gDibuLibreta.addWidget(self.dibu)
    self.libreta = QtGui.QPlainTextEdit()
    gDibuLibreta.addWidget(self.libreta)
    #
    gCalcu = QtGui.QHBoxLayout()
    lCalcu = QtGui.QLabel("CALCULADORA: ")
    self.calcu = QtGui.QLineEdit()
    self.calcu.editingFinished.connect( self.calcular )
    lIgual = QtGui.QLabel(" = ")
    self.resulCalcu = QtGui.QTextBrowser()
    fm = QtGui.QFontMetrics( self.resulCalcu.font() )
    self.resulCalcu.setMaximumHeight( 2*fm.height() )
    gCalcu.addWidget(lCalcu)
    gCalcu.addWidget(self.calcu)
    gCalcu.addWidget(lIgual)
    gCalcu.addWidget(self.resulCalcu)
    #
    grid = QtGui.QVBoxLayout()
    grid.setSpacing(10)
    grid.addItem(gArriba)
    grid.addItem(gProblema)
    grid.addItem(gDibuLibreta)
    grid.addItem(gCalcu)
    #
    self.setLayout(grid)
    #
    self.setWindowTitle("Ejercicios de cinemática")
    self.setFixedSize(900, 700)
    self.show()
    # Parametros del timer
    self.tempo = QtCore.QTimer()
    self.tempo.setInterval(50)
    self.tempo.setSingleShot(False)
    self.tempo.timeout.connect(self.pasoSimulacion)
    #
    self.siguiente()
  #
  def corregir(self):
    self.comprobar(verSolu=True)
  #
  def comprobar(self, verSolu=False):
    cadInfo = ""
    correcto = True
    # Comprueba maxima altura
    if verSolu:
      cadInfo = cadInfo + "Maxima altura:\n"
    mSolu = self.calculaSolucionMaximaAltura()
    for var in mSolu:
      valProf = mSolu[var]
      nomEnt = re.sub("f$", "m", "%s" % var)
      valAlu = -1
      try:
        valAlu = float( self.mEntrada[nomEnt].text() )
      except:
        ###print("ERROR con tipo=%s nomVar=%s" % (tipo, nomEnt))
        pass
      if abs(valProf - valAlu) > 1e-3:
        correcto = False
        if verSolu:
          cadInfo = cadInfo + "  %s = %.3f (NO %.3f)\n" % (var, valProf, valAlu)
      elif verSolu:
        cadInfo = cadInfo + "  %s = %.3f\n" % (var, valProf)
    # Comprueba suelo
    if verSolu:
      cadInfo = cadInfo + "Suelo:\n"
    mSolu = self.calculaSolucionSuelo()
    for var in mSolu:
      valProf = mSolu[var]
      nomEnt = re.sub("f$", "s", "%s" % var)
      valAlu = -1
      try:
        valAlu = float( self.mEntrada[nomEnt].text() )
      except:
        ###print("ERROR con tipo=%s nomVar=%s" % (tipo, nomEnt))
        pass
      if abs(valProf - valAlu) > 1e-3:
        correcto = False
        if verSolu:
          cadInfo = cadInfo + "  %s = %.3f (NO %.3f)\n" % (var, valProf, valAlu)
      elif verSolu:
        cadInfo = cadInfo + "  %s = %.3f\n" % (var, valProf)
    #
    if correcto:
      cadInfo = cadInfo + "==> CORRECTO.\n"
    else:
      cadInfo = cadInfo + "==> MAL.\n"
    #
    self.libreta.setPlainText(cadInfo)
  #
  def calculaSolucionMaximaAltura(self):
    mSolu = {}
    mCond = self.getCondicionesIniciales()
    vyf = 0
    #
    tf = mCond["ti"] + (vyf - mCond["vyi"]) / self.g
    mSolu["tf"] = tf
    #
    dt = tf - mCond["ti"]
    xf = mCond["xi"] + mCond["vxi"] * dt
    mSolu["xf"] = xf
    #
    yf = mCond["yi"] + mCond["vyi"] * dt + 0.5 * self.g * dt * dt
    mSolu["yf"] = yf
    #
    return(mSolu)
  #
  def calculaSolucionSuelo(self):
    mSolu = {}
    mCond = self.getCondicionesIniciales()
    yf = 0
    # (yf - yi) = vyi*dt + 0.5*g*dt^2 => a * dt^2 + b * dt + c = 0
    a = 0.5 * self.g
    b = mCond["vyi"]
    c = -(0.0 - mCond["yi"])
    disc = b*b - 4*a*c
    dt = (-b + math.sqrt(disc)) / (2*a)
    if dt <= 0:
      dt = (-b - math.sqrt(disc)) / (2*a)
    tf = mCond["ti"] + dt
    mSolu["tf"] = tf
    #
    xf = mCond["xi"] + mCond["vxi"] * dt
    mSolu["xf"] = xf
    #
    vyf = mCond["vyi"] + self.g * dt
    mSolu["vyf"] = vyf
    #
    return(mSolu)
  #
  def calculaSolucionTiempo(self, tf):
    mSolu = {}
    mCond = self.getCondicionesIniciales()
    dt = tf - mCond["ti"]
    #
    vyf = mCond["vyi"] + self.g * dt
    mSolu["vyf"] = vyf
    #
    xf = mCond["xi"] + mCond["vxi"] * dt
    mSolu["xf"] = xf
    #
    yf = mCond["yi"] + mCond["vyi"] * dt + 0.5 * self.g * dt * dt
    mSolu["yf"] = yf
    #
    return(mSolu)
  #
  def getCondicionesIniciales(self):
    mIni = {}
    for nomMag in self.vNomMag:
      nomVar = nomMag + 'i'
      valIni = float( self.mEntrada[nomVar].text() )
      mIni[nomVar] = valIni
    return(mIni)
  #
  def simula(self):
    if not self.dibu.dibujar:
      # Prepara datos de la simulacion
      self.tIniSimu = time.time()
      mSoluSuelo = self.calculaSolucionSuelo()
      self.dtMax = mSoluSuelo["tf"]
      mSoluMaxAltura = self.calculaSolucionMaximaAltura()
      mIni = self.getCondicionesIniciales()
      # Pasa los datos a la clase de dibujado
      self.dibu.setLimites(mSoluSuelo["xf"], mSoluMaxAltura["yf"], mIni["yi"])
      #
      self.tempo.start()
      self.dibu.dibujar = True
      self.bSimula.setText("Para simulacion")
    else:
      self.dibu.dibujar = False
      self.bSimula.setText("Arranca simulacion")
  #
  def pasoSimulacion(self):
    dt = time.time() - self.tIniSimu
    if dt <= self.dtMax:
      mSolu = self.calculaSolucionTiempo(dt)
      x = mSolu["xf"]
      y = mSolu["yf"]
      self.dibu.actualiza(x, y)
    else:
      mSolu = self.calculaSolucionTiempo(self.dtMax)
      x = mSolu["xf"]
      y = mSolu["yf"]
      self.dibu.actualiza(x, y)
      #
      self.tempo.stop()
      self.dibu.dibujar = False
      self.bSimula.setText("Arranca simulacion")
  #
  def siguiente(self):
    # Inventa ejercicio de cinematica
    self.mEntrada["ti"].setText("0")
    self.mEntrada["xi"].setText("0")
    self.mEntrada["yi"].setText("%.2f" % random.uniform(self.minYi, self.maxYi))
    self.mEntrada["vxi"].setText("%.2f" % random.uniform(self.minVel, self.maxVel))
    self.mEntrada["vyi"].setText("%.2f" % random.uniform(self.minVel, self.maxVel))
    #
    for tipo in [ 'm', 's' ]:
      for nomMag in self.vNomMag:
        nomVar = nomMag + tipo
        self.mEntrada[nomVar].setText("")
    #
    self.mEntrada["vxm"].setText( self.mEntrada["vxi"].text() )
    self.mEntrada["vxs"].setText( self.mEntrada["vxi"].text() )
    self.mEntrada["vym"].setText("0")
    self.mEntrada["ys"].setText("0")
  #
  def calcular(self):
    try:
      calculo = str( self.calcu.text() )
      self.resulCalcu.setText( str(eval(calculo)) )
    except:
      self.resulCalcu.setText("ERROR")
#
# Programa principal
#
apli = QtGui.QApplication(sys.argv)
ventana = CinematicaQt()
sys.exit( apli.exec_() )
