# Copyright 2021 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Functions to instantiate SimulatedLocalEngines to simulate various Google Devices."""
from typing import cast, Iterable, List, Optional, Union
import pathlib
import time

import google.protobuf.text_format as text_format
import cirq
from cirq_google.api import v2
from cirq_google.engine import (
    calibration,
    engine_validator,
    simulated_local_processor,
)
from cirq_google.devices import serializable_device
from cirq_google.serialization.gate_sets import FSIM_GATESET
from cirq_google.serialization import serializable_gate_set
from cirq_google.engine.simulated_local_engine import SimulatedLocalEngine
from cirq_google.engine.simulated_local_processor import SimulatedLocalProcessor

MOST_RECENT_TEMPLATES = {
    'rainbow': 'rainbow_12_10_2021_device_spec.proto.txt',
    'weber': 'weber_12_10_2021_device_spec.proto.txt',
}

METRICS_1Q = [
    'single_qubit_p00_error',
    'single_qubit_p11_error',
    'single_qubit_readout_separation_error',
    'parallel_p00_error',
    'parallel_p11_error',
    'single_qubit_rb_average_error_per_gate',
    'single_qubit_rb_incoherent_error_per_gate',
    'single_qubit_rb_pauli_error_per_gate',
]

METRICS_2Q = [
    'two_qubit_sycamore_gate_xeb_average_error_per_cycle',
    'two_qubit_sycamore_gate_xeb_pauli_error_per_cycle',
    'two_qubit_sycamore_gate_xeb_incoherent_error_per_cycle',
    'two_qubit_sqrt_iswap_gate_xeb_average_error_per_cycle',
    'two_qubit_sqrt_iswap_gate_xeb_pauli_error_per_cycle',
    'two_qubit_sqrt_iswap_gate_xeb_incoherent_error_per_cycle',
    'two_qubit_parallel_sycamore_gate_xeb_average_error_per_cycle',
    'two_qubit_parallel_sycamore_gate_xeb_pauli_error_per_cycle',
    'two_qubit_parallel_sycamore_gate_xeb_incoherent_error_per_cycle',
    'two_qubit_parallel_sqrt_iswap_gate_xeb_average_error_per_cycle',
    'two_qubit_parallel_sqrt_iswap_gate_xeb_pauli_error_per_cycle',
    'two_qubit_parallel_sqrt_iswap_gate_xeb_incoherent_error_per_cycle',
]
# Technically, T1 for a noiseless simulation should be infinite,
# but we will set it to a very high value.
PERFECT_T1_VALUE = 1_000_000
T1_METRIC_NAME = 'single_qubit_idle_t1_micros'


def _create_perfect_calibration(device: cirq.Device) -> calibration.Calibration:
    all_metrics: calibration.ALL_METRICS = {}
    if device.metadata is None:
        raise ValueError('Devices for noiseless Virtual Engine must have qubits')
    qubit_set = device.metadata.qubit_set
    qubits = [cast(cirq.GridQubit, q) for q in qubit_set]
    for name in METRICS_1Q:
        all_metrics[name] = {(q,): [0.0] for q in qubits}
    for name in METRICS_2Q:
        metric_dict: calibration.METRIC_DICT = {}
        if device.metadata is not None:
            for pair in device.metadata.nx_graph.edges():
                qubits = [cast(cirq.GridQubit, q) for q in pair]
                metric_dict[(qubits[0], qubits[1])] = [0.0]
        all_metrics[name] = metric_dict
    all_metrics[T1_METRIC_NAME] = {(q,): [PERFECT_T1_VALUE] for q in qubits}
    snapshot = v2.metrics_pb2.MetricsSnapshot()
    snapshot.timestamp_ms = int(time.time() * 1000)