import * as React from 'react';
import { Box, Button, ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Grid, Table, TableBody, TableCell, TableHead, TableRow, Typography, Link, MenuItem, TextField, Dialog, DialogTitle, DialogContent, DialogActions, Container } from '@material-ui/core';
import { chosenAlphabet, regexp, inputLoad, myAlert, chosenDatabase, HashMap } from '../basicLayout';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import IsThis from './IsThis';

export interface IAnalyzerProps {
  idInput: string,
  idOutput: string,
  idSwitch: string
}

export interface IAnalyzerState {
  analyzerOutput: string
}

//  KEY for CaesarAnalyzer
export var numberKey: number = 0;
//  Password-Modified PROPS
export var passwordMod: string[] = [''];
//  PROPS into general LanguageSettings
export var atLeastOneChange: boolean = false;
var atbashCounter: number = 0;

//  RESET OF THE PASSWORD FIELD
export function inpurOrLanguageChange() {
  numberKey = 0;
  passwordMod = [];
  atLeastOneChange = false;
  for(var i = 0; i < chosenAlphabet.length; i++)
    passwordMod[i] = '-';
}

export default class Analyzer extends React.Component<IAnalyzerProps, IAnalyzerState> {
  private EngDatabase = new HashMap('E', 12.02, 'T', 9.10, 'A', 8.12, 'O', 7.68, 'I', 7.31, 'N', 6.95, 'S', 6.28, 'R', 6.02, 'H', 5.92, 'D', 4.32, 'L', 3.98, 'U', 2.88,
    'C', 2.71, 'M', 2.61, 'F', 2.30, 'Y', 2.11, 'W', 2.09, 'G', 2.03, 'P', 1.82, 'B', 1.49, 'V', 1.11, 'K', 0.69, 'X', 0.17, 'Q', 0.11, 'J', 0.10, 'Z', 0.07);
  private CzeDatabase = new HashMap('O', 8.667, 'E', 7.695, 'N', 6.535, 'A', 6.219, 'T', 5.727, 'V', 4.662, 'S', 4.516, 'I', 4.353, 'L', 3.842, 'K', 3.737, 'R', 3.697,
    'D', 3.602, 'P', 3.413, 'Í', 3.270, 'M', 3.227, 'U', 3.144, 'Á', 2.236, 'Z', 2.199, 'J', 2.119, 'Y', 1.909, 'Ě', 1.645, 'C', 1.607, 'B', 1.558, 'É', 1.335, 'H', 1.271,
    'Ř', 1.217, 'Ý', 1.072, 'Ž', 0.995, 'Č', 0.949, 'Š', 0.805, 'Ů', 0.695, 'F', 0.273, 'G', 0.273, 'Ú', 0.103, 'Ň', 0.081, 'X', 0.075, 'Ť', 0.043, 'Ó', 0.031, 'Ď', 0.022,
    'W', 0.009, 'Q', 0.001);
  public copyDatabase = new HashMap(this.EngDatabase);
  private input: string = inputLoad();
  private alphabet: any = new HashMap();
  private IC: number = 0;                //  index of coincidence
  private LanguageDatabase = new HashMap('Random text', 0.0385, 'Czech', 0.0510, 'English', 0.0667, 'French', 0.0694, 'Spanish', 0.0729, 'German', 0.0734);

  //  Caesar PROPS
  private caesarKeyMap: any = new HashMap();
  //  Atbash PROPS
  private reverseAlphabet: string[] = chosenAlphabet.slice(0, chosenAlphabet.length).reverse();
  

  constructor(props: IAnalyzerProps) {
    super(props);

    (document.getElementById(this.props.idSwitch) as HTMLInputElement).disabled = false;

    this.state = {
      analyzerOutput: ""
    }

    this.substitutionSettings = this.substitutionSettings.bind(this);
    this.highestCharacterOccurance = this.highestCharacterOccurance.bind(this);
    this.analyzerDialog = this.analyzerDialog.bind(this);
    this.substitutionMenu = this.substitutionMenu.bind(this);
    this.overwiewPaper = this.overwiewPaper.bind(this);
    this.keyComputation = this.keyComputation.bind(this);
  }

  public occurance(arrayInput: string[]) {
    var inputLength: number = 0;
    var alphabet: any = new HashMap();
    arrayInput.forEach(element => {
      if (element.match(regexp)) {
        if (alphabet.has(element))
          alphabet.set(element, alphabet.get(element) + 1);
        else
          alphabet.set(element, 1);
        inputLength++;
      }
    });

    return { inputLength, alphabet };
  }

  private analysisFunction(input: string): any {
    // INITIALIZATION
    var arrayInput = input.toUpperCase().split("");

    // COUNTING OCCURENCE OF EACH CHARACTER 
    var tmp: any = this.occurance(arrayInput);

    // COUNTING PERCENTIGE FOR EACH LETTER
    tmp.alphabet.keys().forEach((e: any) => tmp.alphabet.set(e, Number((tmp.alphabet.get(e) * 100 / tmp.inputLength).toFixed(2))));
    
    return tmp.alphabet;
  }

  private findMax(numberField: number[]): number[] {
    var max: number[] = [0, 0];
    numberField.forEach(e => {
      if (e === max[0])
        max[1]++;
      else if (e > max[0]) {
        max[0] = e;
        max[1] = 0;
      }
    });
    return max;
  }

  private findChars(alphabet: any, value: number[]): string {
    var chars: string = '';
    alphabet.keys().forEach((e: string) => {
      if (alphabet.get(e) === value[0])
        chars += e;
    });
    return chars;
  }

  private printOfCharsWithPerc(percentige: number[], chars: string): string {
    var arrayChars = chars.split('');
    var stringList: string = '';
    arrayChars.forEach( e => {
      e = e.toUpperCase();
      stringList += e + ', ';
    });
    return stringList.slice(0, stringList.length - 2) + " (" + percentige[0] + " %)";
  }

  private highestCharacterOccurance() {
    // FINDING LETTER WITH HIGHEST OCCURENCE
    var maxPercentige: number[] = this.findMax(this.alphabet.values());
    var charsWithMax: string = this.findChars(this.alphabet, maxPercentige);
    charsWithMax = charsWithMax.toUpperCase();

    return "First character with highest occurence is " + this.printOfCharsWithPerc(maxPercentige, charsWithMax) + ".";
  }

  private keyComputation(cipherLetter: string, substLetter: string): boolean {
    //  PASSWORD-MODIFIED PASSWORD COUNTER
    var bool: boolean = false;
    if(passwordMod.includes(cipherLetter)) {
      bool = Boolean(myAlert('You are reassigning "' + cipherLetter + '" to "' + substLetter + '" (now assigned to "' + chosenAlphabet[passwordMod.indexOf(cipherLetter)] + '")! Proceed?', 'conf'));
      if(bool)
        passwordMod[passwordMod.indexOf(cipherLetter)] = "-";
      else
        return true;
    } else if(passwordMod[chosenAlphabet.indexOf(substLetter)] !== '-') {
      bool = Boolean(myAlert('You are trying to change cipher letter "' + passwordMod[chosenAlphabet.indexOf(substLetter)] + '" to "' + cipherLetter + '" among password cipher! Proceed?', 'conf'));
      if(!bool)
        return true;
    }

    //  ATBASH ALPHABET CORRESPONDENCE COUNTER
    if(this.reverseAlphabet.indexOf(cipherLetter) === chosenAlphabet.indexOf(substLetter))
      atbashCounter++;
    else if(atbashCounter !== 0) {
      bool = Boolean(myAlert('From previous substitutions were counted ' + atbashCounter + ' correspondences with the Atbash cipher alphabet! Now the positions have not met. Proceed?', 'conf'));
      if(!bool)
        return true;
    }
    
    passwordMod[chosenAlphabet.indexOf(substLetter)] = cipherLetter;
    //  NUMBER KEY FOR CAESAR CIPHER
    var tmp: number = chosenAlphabet.indexOf(substLetter) - chosenAlphabet.indexOf(cipherLetter);

    if(this.caesarKeyMap.has(tmp))
      this.caesarKeyMap.set(tmp, this.caesarKeyMap.get(tmp) + 1);
    else
      this.caesarKeyMap.set(tmp, 1);

    var sortedValues: number[] = this.caesarKeyMap.values().sort();
    numberKey = this.caesarKeyMap.search(sortedValues[sortedValues.length - 1]);
    
    return false;
  }

  private letterSubstitution() {
    // TODO: zvýraznění změn
    atLeastOneChange = true;
    var arrayInput: string[] = this.input.toLowerCase().split('');
    var arrayOutput: string[] = [''];
    if(document.getElementById(this.props.idOutput) && (document.getElementById(this.props.idOutput) as HTMLInputElement).innerHTML !== '')
      arrayOutput = (document.getElementById(this.props.idOutput) as HTMLInputElement).innerHTML.split('');
    else
      arrayOutput = arrayInput;

    var cipherLetter: string = '';
    var substLetter: string = '';

    if ((document.getElementById("cipherLetter") && document.getElementById("substituteLetter"))) {
      cipherLetter = (document.getElementById("cipherLetter") as HTMLInputElement).innerText.charAt(0);
      substLetter = (document.getElementById("substituteLetter") as HTMLInputElement).innerText.charAt(0);
    }

    if(cipherLetter.charCodeAt(0) === 8203 || substLetter.charCodeAt(0) === 8203) {   //  ACII(8203) === ZERO WIDTH SPACE
      myAlert('Please define the substitution!', 'error', 'Wrong definition of subtitution!');
      return;
    }

    var stop: boolean = false;
    stop = this.keyComputation(cipherLetter.toLocaleLowerCase(), substLetter.toLocaleLowerCase());

    if(stop)
      return;
    
    arrayInput.forEach((e, index) => {
      if (e.toUpperCase() === cipherLetter) {
        e = substLetter.toUpperCase();
        arrayOutput[index] = e;
      }
    });

    if (document.getElementById(this.props.idOutput))
      (document.getElementById(this.props.idOutput) as HTMLInputElement).innerHTML = arrayOutput.join('');
    
    
  }

  // SELECTORS FOR THE LETTER SUBSTITUTION
  private substitutionSettings() {
    const [s_cipher, s_setCipher] = React.useState('');
    const [s_subst, s_setSubst] = React.useState('');

    const s_handleCipher = (event: React.ChangeEvent<HTMLInputElement>) => {
      s_setCipher(event.target.value);
    }
    const s_handleSubst = (event: React.ChangeEvent<HTMLInputElement>) => {
      s_setSubst(event.target.value);
    }

    return (
      <form noValidate autoComplete="off">
        <div>
          <Grid container className='rowGrid'>
            <Box marginRight={5} marginLeft={5}>
              <TextField
                id="cipherLetter"
                select
                value={s_cipher}
                onChange={s_handleCipher}
                label="Ciphertext letter"
                helperText="Please select ciphertext letter"
              >
                {this.alphabet.keys().map((e: any) =>
                  <MenuItem key={e} value={e}> <b>{e}</b>{" (" + this.alphabet.get(e) + "% )"}</MenuItem>
                )}
              </TextField>
            </Box>
            <Box marginRight={5} marginLeft={5}>
              <TextField
                id="substituteLetter"
                select
                value={s_subst}
                onChange={s_handleSubst}
                label="Substitute letter"
                helperText="Please select substituting letter"
              >
                {this.copyDatabase.keys().map((e: any) =>
                  <MenuItem key={e} value={e}> <b>{e}</b>{" (" + this.copyDatabase.get(e) + "% )"} </MenuItem>
                )}
              </TextField>
            </Box>
          </Grid>
        </div>
      </form>
    );
  }

  private analyzerDialog () {
    const [open, setOpen] = React.useState(false);
    
    const handleClickOpen = () => {
      setOpen(true);
    };
    const handleClose = () => {
      setOpen(false);
    };

    var key: number = 0;
    return (
      <div>
        <Button variant="outlined" color="secondary" onClick={handleClickOpen}>Frequency Analysis tables</Button>
        
        <Dialog
          open={open}
          onClose={handleClose}>
          <DialogTitle>Results of frequency analysis of english and czech</DialogTitle>
          <DialogContent>
            <Grid container className='columnGrid'>
              <Typography variant='caption' align='center' style={{ color: '#808080', lineHeight: 0.5 }}>
                <p>Sources of each databses are linked on the title of each table.</p>
                <p>Both sources were last checked on <i>25th of May 2020</i>.</p>
              </Typography>
              <Grid container className='rowGrid'>
                <Box my={3}>
                  <Container maxWidth='md'>
                    <Typography>
                      <Link color="secondary" href="http://www.mensa.cz/volny-cas/hlavolamy/detska-sifrovaci-liga/frekvencni-tabulka-pismen" target="_blank" rel="noopener noreferrer">
                        CZECH FREQUENCY
                      </Link>
                    </Typography>
                    <Table className="table" size='small'>
                      {this.CzeDatabase.keys().sort().map((e: any) =>
                        <TableRow key={Math.random() * key++}>
                          <TableCell variant='head' key={e} align="center" component="th" scope="row">
                            { e }
                          </TableCell>
                          <TableCell key={e} align="center" component="th" scope="row">
                            { this.CzeDatabase.get(e) }
                          </TableCell>
                        </TableRow>)}
                    </Table>
                  </Container>
                </Box>

                <Box my={3}>
                  <Container maxWidth='md'>
                    <Typography>
                      <Link color="secondary" href="http://pi.math.cornell.edu/~mec/2003-2004/cryptography/subs/frequencies.html" target="_blank" rel="noopener noreferrer">
                        ENGLISH FREQUENCY
                      </Link>
                    </Typography>
                    <Table className="table" size='small'>
                      {this.EngDatabase.keys().sort().map((e: any) =>
                        <TableRow key={Math.random() * key++}>
                          <TableCell variant='head' key={e} align="center" component="th" scope="row">
                            { e }
                          </TableCell>
                          <TableCell key={e} align="center" component="th" scope="row">
                            { this.EngDatabase.get(e) }
                          </TableCell>
                        </TableRow>)}
                    </Table>
                  </Container>
                </Box>
              </Grid>
            </Grid>
          </DialogContent>

          <DialogActions style={{alignSelf: 'center'}}>
            <Box my={3}>
              <Button onClick={handleClose} color="secondary" autoFocus>CLOSE</Button>
            </Box>
          </DialogActions>
        </Dialog>
      </div>
    );
  }

  private substitutionMenu() {
    return(
      <Box my={5}>
        <Box marginBottom={2}>
          <Typography component="legend" color="secondary">FREQUENCY ANALYSIS MENU</Typography>
        </Box>
        
        <this.substitutionSettings />

        <Box marginTop={3}>
          <Button
            id='subsButton'
            size="large"
            variant="contained"
            autoFocus
            onClick={ () => {
              this.letterSubstitution();
              if(document.getElementById('myNumberField'))
                (document.getElementById('myNumberField') as HTMLInputElement).value = String(numberKey);
              this.forceUpdate();
            }}>Substitute!</Button>
        </Box>

        <Box my={2}>
          <Button
            id='restartButton'
            fullWidth={false}
            style={{backgroundColor: '#ffffff'}}
            size="small"
            variant="outlined"
            onClick={ () => {
              (document.getElementById(this.props.idOutput) as HTMLInputElement).innerHTML = '';
              myAlert('Analyzer restarted!', 'info');
              numberKey = 0;
              atbashCounter = 0;
              this.caesarKeyMap = new HashMap();
              passwordMod = [''];
              atLeastOneChange = false;
              this.forceUpdate();
            }}>Restart Analysis</Button>
        </Box>
        <Box marginTop={5} style={{ backgroundColor: '#ffffff' }}>
          <Typography variant='caption' align='left' color='textSecondary' style={{ lineHeight: 1.2 }}>
              Stuck? Here are ranked lists of most used words:
              <ul>
                <li>Czech words: <Link color='primary' href='https://matematika.cz/frekvencni-analyza#nejcastejsi-slova-jazyka' target="_blank" rel="noopener noreferrer">Matematika.cz</Link></li>
                <li>English words: <Link color='primary' href='https://www.wordfrequency.info/free.asp?s=y' target="_blank" rel="noopener noreferrer">WordFrequency.info</Link></li>
              </ul>
          </Typography>
        </Box>
      </Box>
    );
  }

  //  COUNTING INDEX OF COINCIDENCE
  public indexOfCoincidence(input: string): number {
    var sum: number = 0;
    var N: number[] = this.occurance(input.toLocaleLowerCase().split('')).alphabet.values();
    var IC: number = 0;
    N.forEach((e: any) => sum += e);

    N.forEach((e: any) => {
      IC += e * (e - 1);
    });

    this.IC = IC/(sum * (sum - 1));
    return chosenAlphabet.length * this.IC;
  }

  //  PROBABLE CIPHER DETERMINATION
  private cipherDetermination() {
    if( Math.abs(this.IC - 0.07) < Math.abs(this.IC - 0.0385) )
      return 'monoalphabetic';
    else
      return 'polyalphabetic';
  }

  //  PROBABLE LANGUAGE DETERMINATION
  private languageDetermination(): string {
    var min: number = Number.MAX_VALUE;
    var language: string = 'unknown';
    
    this.LanguageDatabase.forEach((value:number, key:string) => {
      var tmpRest: number = Math.abs(value - this.IC);
      if(min > tmpRest) {
        min = tmpRest
        language = key;
      }
    });
    
    return language;
  }

  private overwiewPaper() {
    return(
      <div>
          <Box marginTop={3} textAlign='left' boxShadow={2}>
            <Table id='table' className="table" padding="default">
              <TableBody>
                <TableRow>
                  <TableCell variant='head'>Index of coincidence:</TableCell><TableCell><b>{this.indexOfCoincidence(this.input)}</b></TableCell>
                </TableRow>
                <TableRow>
                  <TableCell variant='head'>Probable type of substitution cipher:</TableCell><TableCell>{this.cipherDetermination()}</TableCell>
                </TableRow>
                <TableRow>
                  <TableCell variant='head'>Probable language:</TableCell><TableCell>{this.languageDetermination()}</TableCell>
                </TableRow>
                <TableRow>
                  <TableCell variant='head'>Counted Caesar key:</TableCell><TableCell>{numberKey}</TableCell>
                </TableRow>
                <TableRow>
                  <TableCell variant='head'>Atbash correspondence counter:</TableCell><TableCell>{atbashCounter}</TableCell>
                </TableRow>
                <TableRow>
                  <TableCell variant='head'>Password alphabet:</TableCell><TableCell>{passwordMod.join(',')}</TableCell>
                </TableRow>
              </TableBody>
            </Table>
          </Box>
      </div>
    );
  }

  public render() {
    if(chosenDatabase === 'cze')
      this.copyDatabase = new HashMap(this.CzeDatabase);
    else if(chosenDatabase === 'eng')
      this.copyDatabase = new HashMap(this.EngDatabase);
    else
      myAlert('An error occured among the frequecy databases selector!', 'error', chosenDatabase)
      
    //  CONSTANTS FOR INPUT ANALYSIS TABLE
    this.input = inputLoad();
    this.alphabet = this.analysisFunction(this.input);
    this.highestCharacterOccurance();

    if(passwordMod.length !== chosenAlphabet.length) {
      inpurOrLanguageChange(); 
    }
    
    var key = 0;
    const tableCellsELements = this.alphabet.keys().map((e: React.ReactNode) =>
      <TableCell key={key++} align="center" component="th" scope="row" style={{ padding: 3 }}>
        {e}
      </TableCell>
    );
    const tableCellsPercentige = this.alphabet.values().map((e: React.ReactNode) =>
      <TableCell key={key++} align="center" component="th" scope="row" style={{ padding: 3 }}>
        {e + ' %'}
      </TableCell>
    );

    return (
      <div>
        <Grid
          container
          className='columnGrid'
        >
          <Box my={2}>
            <Box
              marginBottom={3}
              alignItems='center'
            >
              <Typography variant="h6" color="secondary" gutterBottom >
                <b>Character analysis</b>
              </Typography>
            </Box>

            <Box boxShadow={3}>
              <Table id='table' className="table" padding="default">
                <TableHead>
                  <TableRow>{tableCellsELements}</TableRow>
                </TableHead>
                <TableBody>
                  <TableRow>{tableCellsPercentige}</TableRow>
                </TableBody>
              </Table>
            </Box>

            <Box marginTop={2}>
              <Typography id='mainStatement'>{this.highestCharacterOccurance()}</Typography>
            </Box>

            <Grid container className='rowGrid'>
              <this.substitutionMenu />
              <this.overwiewPaper />
            </Grid>

            <Box marginTop={5}>
              <IsThis idInput={this.props.idInput} idOutput={this.props.idOutput} idSwitch={this.props.idSwitch}/>
            </Box>

            <Box marginTop={5}>
              <ExpansionPanel>
                <ExpansionPanelSummary
                  id="aboutAnalyzer"
                  expandIcon={<ExpandMoreIcon />}
                >
                  <Typography
                    color="primary"
                    className='heading'
                  ><b>ABOUT Analyzer</b></Typography>
                </ExpansionPanelSummary>
                <ExpansionPanelDetails>
                  <Typography style={{ color: '#808080' }} align='left' component={"div"}>
                    <p>
                      <b>Frequency analysis</b> is one of a basic method used in cryptoanalysis to encrypt ciphertext.
                      Principle of this method is to compare frequency analysis of the ciphertext with an analysis of a chosen language.
                    </p>
                    
                    <p>
                      This method also allows to mine some information about cipher, that was used to create analyzed ciphertext. 
                      For such mining is used <b>index of coincidence</b> (also known as <i>Friedmann's test</i>). Counted index and gained informations from the result can be seen in the table above. 
                      Expected values and other information about the index of coincidence were taken from{' '}
                      <Link color="secondary" href="http://alexbarter.com/statistics/index-of-coincidence/" target="_blank" rel="noopener noreferrer">here</Link>.
                    </p>

                    <div style={{textAlign: "center"}}><this.analyzerDialog /></div>

                    <p>
                      Vigenère Analyzer uses the database of bigrams taken form 
                      <Link color="secondary" href="http://practicalcryptography.com/cryptanalysis/letter-frequencies-various-languages/english-letter-frequencies/" target="_blank" rel="noopener noreferrer"> here</Link>. 
                      These databses of frequencies were generated from cca 4.5 billion characters of English text, sourced from{' '}
                      <Link color="secondary" href="https://wortschatz.uni-leipzig.de/en/download/" target="_blank" rel="noopener noreferrer">Wortschatz</Link>.
                    </p>
                  </Typography>
                </ExpansionPanelDetails>
              </ExpansionPanel>
            </Box>
          </Box>
        </Grid>
      </div >
    );
  }
}